From 52a2892226d8891d994e099ed4f79b8908519fcd Mon Sep 17 00:00:00 2001 From: JanMalch Date: Sun, 27 Oct 2019 21:06:29 +0100 Subject: [PATCH 01/10] feat: add coercion for string arrays --- src/cdk/coercion/public-api.ts | 1 + src/cdk/coercion/string-array.spec.ts | 28 +++++++++++++++++++++++++++ src/cdk/coercion/string-array.ts | 21 ++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/cdk/coercion/string-array.spec.ts create mode 100644 src/cdk/coercion/string-array.ts diff --git a/src/cdk/coercion/public-api.ts b/src/cdk/coercion/public-api.ts index a33a133e56bb..69d9eada7376 100644 --- a/src/cdk/coercion/public-api.ts +++ b/src/cdk/coercion/public-api.ts @@ -11,3 +11,4 @@ export * from './number-property'; export * from './array'; export * from './css-pixel-value'; export * from './element'; +export * from './string-array'; diff --git a/src/cdk/coercion/string-array.spec.ts b/src/cdk/coercion/string-array.spec.ts new file mode 100644 index 000000000000..d22f1be0953e --- /dev/null +++ b/src/cdk/coercion/string-array.spec.ts @@ -0,0 +1,28 @@ +import {coerceStringArray} from '@angular/cdk/coercion/string-array'; + +describe('coerceStringArray', () => { + it('should split a string', () => { + expect(coerceStringArray('x,y, z,1')).toEqual(['x', 'y', 'z', '1']); + }); + + it('should map values to string in an array', () => { + expect(coerceStringArray(['x', 1, true, null, undefined, ['arr', 'ay'], { data: false }])) + .toEqual(['x', '1', 'true', 'null', 'undefined', 'arr,ay', '[object Object]']); + }); + + it('should trim values and remove empty values', () => { + expect(coerceStringArray(', x, ,, ')).toEqual(['x']); + }); + + it('should map non-string values to string', () => { + expect(coerceStringArray(0)).toEqual(['0']); + }); + + it('should return an empty array for null', () => { + expect(coerceStringArray(null)).toEqual([]); + }); + + it('should return an empty array for undefined', () => { + expect(coerceStringArray(undefined)).toEqual([]); + }); +}); diff --git a/src/cdk/coercion/string-array.ts b/src/cdk/coercion/string-array.ts new file mode 100644 index 000000000000..43b5d47aeb43 --- /dev/null +++ b/src/cdk/coercion/string-array.ts @@ -0,0 +1,21 @@ +/** + * @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 + */ + +/** Coerces a value to an array of strings. */ +export function coerceStringArray(value: any, separator: string | RegExp = ','): string[] { + if (value == null) { + return []; + } + const stringArray = Array.isArray(value) ? + value.map(item => `${item}`) : + value.toString().split(separator); + return stringArray + .map((item: string) => item.trim()) + .filter((item: string) => item.length > 0); +} + From cd0639fab6af9deb25224e2d2e73a33f651edc1b Mon Sep 17 00:00:00 2001 From: JanMalch Date: Wed, 20 Nov 2019 18:31:36 +0100 Subject: [PATCH 02/10] refactor(coercion): use space as default separator for coerceStringArray Changes the default separator for coerceStringArray to a single space, to be better suited for the common usages like CSS class inputs. Improve documentation to better describe behaviour and use cases. --- src/cdk/coercion/string-array.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cdk/coercion/string-array.ts b/src/cdk/coercion/string-array.ts index 43b5d47aeb43..204b9d861d2f 100644 --- a/src/cdk/coercion/string-array.ts +++ b/src/cdk/coercion/string-array.ts @@ -6,8 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -/** Coerces a value to an array of strings. */ -export function coerceStringArray(value: any, separator: string | RegExp = ','): string[] { +/** + * Coerces a value to an array of trimmed non-empty strings. + * Useful for defining CSS classes or table columns. + */ +export function coerceStringArray(value: any, separator: string | RegExp = ' '): string[] { if (value == null) { return []; } From 174aac86e220f4d468c0ba0c758edcc794902068 Mon Sep 17 00:00:00 2001 From: JanMalch Date: Wed, 20 Nov 2019 18:35:32 +0100 Subject: [PATCH 03/10] feat(autocomplete): use coerceStringArray for CSS class input With coerceStringArray no empty string key will be added to the classList object. The input can also accept string[] now. --- src/material/autocomplete/autocomplete.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/material/autocomplete/autocomplete.ts b/src/material/autocomplete/autocomplete.ts index e3ddc609119a..6db7419a0675 100644 --- a/src/material/autocomplete/autocomplete.ts +++ b/src/material/autocomplete/autocomplete.ts @@ -7,7 +7,7 @@ */ import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {coerceBooleanProperty, coerceStringArray} from '@angular/cdk/coercion'; import { AfterContentInit, ChangeDetectionStrategy, @@ -155,10 +155,10 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC * inside the overlay container to allow for easy styling. */ @Input('class') - set classList(value: string) { + set classList(value: string | string[]) { if (value && value.length) { - this._classList = value.split(' ').reduce((classList, className) => { - classList[className.trim()] = true; + this._classList = coerceStringArray(value).reduce((classList, className) => { + classList[className] = true; return classList; }, {} as {[key: string]: boolean}); } else { From c4da94d5bd6b53cf690e81642b1a094e54e5cd19 Mon Sep 17 00:00:00 2001 From: JanMalch Date: Tue, 26 Nov 2019 18:36:49 +0100 Subject: [PATCH 04/10] refactor(datepicker): use coerceStringArray for panelClass input --- src/material/datepicker/datepicker.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/material/datepicker/datepicker.ts b/src/material/datepicker/datepicker.ts index f675edf4cce3..f084df748a01 100644 --- a/src/material/datepicker/datepicker.ts +++ b/src/material/datepicker/datepicker.ts @@ -7,7 +7,7 @@ */ import {Directionality} from '@angular/cdk/bidi'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {coerceBooleanProperty, coerceStringArray} from '@angular/cdk/coercion'; import {ESCAPE, UP_ARROW} from '@angular/cdk/keycodes'; import { Overlay, @@ -210,8 +210,21 @@ export class MatDatepicker implements OnDestroy, CanColor { */ @Output() readonly monthSelected: EventEmitter = new EventEmitter(); - /** Classes to be passed to the date picker panel. Supports the same syntax as `ngClass`. */ - @Input() panelClass: string | string[]; + private _panelClass: string[]; + + /** Classes to be passed to the date picker panel. */ + get panelClass(): string | string[] { + return this._panelClass; + } + + /** + * Classes to be passed to the date picker panel. + * Supports string and string array values, similar to `ngClass`. + */ + @Input() + set panelClass(value: string | string[]) { + this._panelClass = coerceStringArray(value); + } /** Function that can be used to add custom CSS classes to dates. */ @Input() dateClass: (date: D) => MatCalendarCellCssClasses; From 72daa7b3d24443255fbd7bb4b63de2065426d15b Mon Sep 17 00:00:00 2001 From: JanMalch Date: Mon, 9 Dec 2019 20:59:52 +0100 Subject: [PATCH 05/10] refactor(coercion): improve coerceStringArray implementation The function now only requires one loop over the array instead of two. Improve documentation by adding parameter description and behaviour for special cases. Minor style improvements. --- src/cdk/coercion/string-array.spec.ts | 38 +++++++++++++-------------- src/cdk/coercion/string-array.ts | 33 ++++++++++++++++------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/cdk/coercion/string-array.spec.ts b/src/cdk/coercion/string-array.spec.ts index d22f1be0953e..48a414a5352a 100644 --- a/src/cdk/coercion/string-array.spec.ts +++ b/src/cdk/coercion/string-array.spec.ts @@ -1,28 +1,28 @@ import {coerceStringArray} from '@angular/cdk/coercion/string-array'; describe('coerceStringArray', () => { - it('should split a string', () => { - expect(coerceStringArray('x,y, z,1')).toEqual(['x', 'y', 'z', '1']); - }); + it('should split a string', () => { + expect(coerceStringArray('x y z 1')).toEqual(['x', 'y', 'z', '1']); + }); - it('should map values to string in an array', () => { - expect(coerceStringArray(['x', 1, true, null, undefined, ['arr', 'ay'], { data: false }])) - .toEqual(['x', '1', 'true', 'null', 'undefined', 'arr,ay', '[object Object]']); - }); + it('should map values to string in an array', () => { + expect(coerceStringArray(['x', 1, true, null, undefined, ['arr', 'ay'], { data: false }])) + .toEqual(['x', '1', 'true', 'null', 'undefined', 'arr,ay', '[object Object]']); + }); - it('should trim values and remove empty values', () => { - expect(coerceStringArray(', x, ,, ')).toEqual(['x']); - }); + it('should trim values and remove empty values', () => { + expect(coerceStringArray(', x, ,, ', ',')).toEqual(['x']); + }); - it('should map non-string values to string', () => { - expect(coerceStringArray(0)).toEqual(['0']); - }); + it('should map non-string values to string', () => { + expect(coerceStringArray(0)).toEqual(['0']); + }); - it('should return an empty array for null', () => { - expect(coerceStringArray(null)).toEqual([]); - }); + it('should return an empty array for null', () => { + expect(coerceStringArray(null)).toEqual([]); + }); - it('should return an empty array for undefined', () => { - expect(coerceStringArray(undefined)).toEqual([]); - }); + it('should return an empty array for undefined', () => { + expect(coerceStringArray(undefined)).toEqual([]); + }); }); diff --git a/src/cdk/coercion/string-array.ts b/src/cdk/coercion/string-array.ts index 204b9d861d2f..652cb36d8dfe 100644 --- a/src/cdk/coercion/string-array.ts +++ b/src/cdk/coercion/string-array.ts @@ -8,17 +8,32 @@ /** * Coerces a value to an array of trimmed non-empty strings. + * Anything that is not an array or null-ish will be turned into a string via a template literal. + * Null-ish values will result in an empty array. + * This results in the following outcomes: + * - `null` -> `[]` + * - `[null]` -> `["null"]` + * - `[1, [2, 3]]` -> `["1", "2,3"]` + * - `[{ a: 0 }]` -> `["[object Object]"]` + * - `{ a: 0 }` -> `["[object", "Object]"]` + * * Useful for defining CSS classes or table columns. + * @param value the value to coerce into an array of strings + * @param separator split-separator if value isn't an array */ -export function coerceStringArray(value: any, separator: string | RegExp = ' '): string[] { - if (value == null) { - return []; +export function coerceStringArray(value: any, separator: string | RegExp = /\s+/): string[] { + const result = []; + + if (value != null) { + const sourceValues = Array.isArray(value) ? value : `${value}`.split(separator); + for (const sourceValue of sourceValues) { + const trimmedString = `${sourceValue}`.trim(); + if (!!trimmedString) { + result.push(trimmedString); + } } - const stringArray = Array.isArray(value) ? - value.map(item => `${item}`) : - value.toString().split(separator); - return stringArray - .map((item: string) => item.trim()) - .filter((item: string) => item.length > 0); + } + + return result; } From c3747bde041ba73566e833a888e91b93aa0571b2 Mon Sep 17 00:00:00 2001 From: JanMalch Date: Mon, 9 Dec 2019 22:25:23 +0100 Subject: [PATCH 06/10] test(datepicker): add test for panelClass Add tests for panelClass to check coercion and adding it onto the target element. Fixes minor style issue in datepicker source. --- src/material/datepicker/datepicker.spec.ts | 53 ++++++++++++++++++++++ src/material/datepicker/datepicker.ts | 9 +--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index 23784b20251b..21f237f62b4d 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -1713,6 +1713,47 @@ describe('MatDatepicker', () => { })); }); + describe('panelClass input', () => { + let fixture: ComponentFixture; + let testComponent: PanelClassDatepicker; + + beforeEach(fakeAsync(() => { + fixture = createComponent(PanelClassDatepicker, [MatNativeDateModule]); + fixture.detectChanges(); + + testComponent = fixture.componentInstance; + })); + + afterEach(fakeAsync(() => { + testComponent.datepicker.close(); + fixture.detectChanges(); + flush(); + })); + + it('should accept a single class', () => { + testComponent.panelClass = 'foobar'; + fixture.detectChanges(); + expect(testComponent.datepicker.panelClass).toEqual(['foobar']); + }); + + it('should accept multiple classes', () => { + testComponent.panelClass = 'foo bar'; + fixture.detectChanges(); + expect(testComponent.datepicker.panelClass).toEqual(['foo', 'bar']); + }); + + it('should work with ngClass', () => { + testComponent.panelClass = ['foo', 'bar']; + testComponent.datepicker.open(); + fixture.detectChanges(); + + const datepickerContent = testComponent.datepicker['_dialogRef']!!.componentInstance; + const actualClasses = datepickerContent._elementRef.nativeElement.children[1].classList; + expect(actualClasses.contains('foo')).toBe(true); + expect(actualClasses.contains('bar')).toBe(true); + }); + }); + }); @@ -2021,3 +2062,15 @@ class DatepickerToggleWithNoDatepicker {} `, }) class DatepickerInputWithNoDatepicker {} + +@Component({ + template: ` + + + `, +}) +class PanelClassDatepicker { + date = new Date(0); + panelClass: any; + @ViewChild('d') datepicker: MatDatepicker; +} diff --git a/src/material/datepicker/datepicker.ts b/src/material/datepicker/datepicker.ts index f084df748a01..67703f60caee 100644 --- a/src/material/datepicker/datepicker.ts +++ b/src/material/datepicker/datepicker.ts @@ -210,21 +210,16 @@ export class MatDatepicker implements OnDestroy, CanColor { */ @Output() readonly monthSelected: EventEmitter = new EventEmitter(); - private _panelClass: string[]; - - /** Classes to be passed to the date picker panel. */ - get panelClass(): string | string[] { - return this._panelClass; - } - /** * Classes to be passed to the date picker panel. * Supports string and string array values, similar to `ngClass`. */ @Input() + get panelClass(): string | string[] { return this._panelClass; } set panelClass(value: string | string[]) { this._panelClass = coerceStringArray(value); } + private _panelClass: string[]; /** Function that can be used to add custom CSS classes to dates. */ @Input() dateClass: (date: D) => MatCalendarCellCssClasses; From f7d28e2cfba83c612bac792d6b97e12e3e4cc47f Mon Sep 17 00:00:00 2001 From: JanMalch Date: Fri, 20 Dec 2019 06:54:49 +0100 Subject: [PATCH 07/10] test(coercion): add coerceStringArray test for custom delimiter Also improves the documentation for coerceStringArray by adding more examples and clarifying its behaviour. Removes an unnecessary !! to check for an empty string. --- src/cdk/coercion/string-array.spec.ts | 4 ++++ src/cdk/coercion/string-array.ts | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cdk/coercion/string-array.spec.ts b/src/cdk/coercion/string-array.spec.ts index 48a414a5352a..99bb3b3da5c8 100644 --- a/src/cdk/coercion/string-array.spec.ts +++ b/src/cdk/coercion/string-array.spec.ts @@ -10,6 +10,10 @@ describe('coerceStringArray', () => { .toEqual(['x', '1', 'true', 'null', 'undefined', 'arr,ay', '[object Object]']); }); + it('should work with a custom delimiter', () => { + expect(coerceStringArray('1::2::3::4', '::')).toEqual(['1', '2', '3', '4']); + }); + it('should trim values and remove empty values', () => { expect(coerceStringArray(', x, ,, ', ',')).toEqual(['x']); }); diff --git a/src/cdk/coercion/string-array.ts b/src/cdk/coercion/string-array.ts index 652cb36d8dfe..23c17e5b10dc 100644 --- a/src/cdk/coercion/string-array.ts +++ b/src/cdk/coercion/string-array.ts @@ -8,11 +8,13 @@ /** * Coerces a value to an array of trimmed non-empty strings. - * Anything that is not an array or null-ish will be turned into a string via a template literal. - * Null-ish values will result in an empty array. + * Any input that is not an array, `null` or `undefined` will be turned into a string + * via `toString()` and subsequently split with the given separator. + * `null` and `undefined` will result in an empty array. * This results in the following outcomes: * - `null` -> `[]` * - `[null]` -> `["null"]` + * - `["a", "b ", " "]` -> `["a", "b"]` * - `[1, [2, 3]]` -> `["1", "2,3"]` * - `[{ a: 0 }]` -> `["[object Object]"]` * - `{ a: 0 }` -> `["[object", "Object]"]` @@ -28,7 +30,7 @@ export function coerceStringArray(value: any, separator: string | RegExp = /\s+/ const sourceValues = Array.isArray(value) ? value : `${value}`.split(separator); for (const sourceValue of sourceValues) { const trimmedString = `${sourceValue}`.trim(); - if (!!trimmedString) { + if (trimmedString) { result.push(trimmedString); } } From 93496a024dc2df22984790a9d47d8acf7ca0c37e Mon Sep 17 00:00:00 2001 From: JanMalch Date: Thu, 30 Jan 2020 21:23:00 +0100 Subject: [PATCH 08/10] fix: add missing declarations to public_api_guard --- tools/public_api_guard/cdk/coercion.d.ts | 2 ++ tools/public_api_guard/material/autocomplete.d.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/public_api_guard/cdk/coercion.d.ts b/tools/public_api_guard/cdk/coercion.d.ts index 1cf0d8e786ec..97604b4e9a9c 100644 --- a/tools/public_api_guard/cdk/coercion.d.ts +++ b/tools/public_api_guard/cdk/coercion.d.ts @@ -13,4 +13,6 @@ export declare function coerceElement(elementOrRef: ElementRef | T): T; export declare function coerceNumberProperty(value: any): number; export declare function coerceNumberProperty(value: any, fallback: D): number | D; +export declare function coerceStringArray(value: any, separator: string | RegExp = /\s+/): string[]; + export declare type NumberInput = string | number | null | undefined; diff --git a/tools/public_api_guard/material/autocomplete.d.ts b/tools/public_api_guard/material/autocomplete.d.ts index b5584e8d70d3..41d87b3d709c 100644 --- a/tools/public_api_guard/material/autocomplete.d.ts +++ b/tools/public_api_guard/material/autocomplete.d.ts @@ -27,7 +27,7 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement _isOpen: boolean; _keyManager: ActiveDescendantKeyManager; autoActiveFirstOption: boolean; - classList: string; + classList: string | string[]; readonly closed: EventEmitter; displayWith: ((value: any) => string) | null; id: string; From ffd7f0d040da3830d722021776879bf63a9882d7 Mon Sep 17 00:00:00 2001 From: JanMalch Date: Fri, 31 Jan 2020 16:51:09 +0100 Subject: [PATCH 09/10] fix: update fork and fix declaration in public_api_guard --- .circleci/config.yml | 28 +- .github/CODEOWNERS | 3 + BUILD.bazel | 15 + CHANGELOG.md | 98 + FAQ.md | 4 +- LICENSE | 2 +- README.md | 27 +- WORKSPACE | 20 +- .../creating-a-custom-form-field-control.md | 6 +- guides/v9-hammerjs-migration.md | 48 +- package.json | 61 +- packages.bzl | 43 +- rollup-globals.bzl | 2 +- scripts/check-entry-point-setup.js | 50 + scripts/run-component-tests.js | 107 ++ src/a11y-demo/dialog/dialog-neptune-a11y.html | 2 +- .../progress-spinner-a11y.html | 2 +- src/bazel-tsconfig-build.json | 1 + src/cdk-experimental/dialog/BUILD.bazel | 1 - src/cdk-experimental/dialog/dialog-module.ts | 2 - .../popover-edit/edit-event-dispatcher.ts | 22 +- .../popover-edit/lens-directives.ts | 23 +- .../popover-edit/popover-edit.spec.ts | 33 +- .../popover-edit/table-directives.ts | 23 +- src/cdk/BUILD.bazel | 2 +- src/cdk/a11y/BUILD.bazel | 1 - src/cdk/a11y/_a11y.scss | 59 +- src/cdk/a11y/a11y-module.ts | 3 +- .../configurable-focus-trap-config.ts | 11 + .../configurable-focus-trap-factory.ts | 54 + .../configurable-focus-trap.spec.ts | 127 ++ .../focus-trap/configurable-focus-trap.ts | 63 + .../event-listener-inert-strategy.spec.ts | 96 + .../event-listener-inert-strategy.ts | 66 + .../focus-trap/focus-trap-inert-strategy.ts | 26 + .../focus-trap/focus-trap-manager.spec.ts | 48 + src/cdk/a11y/focus-trap/focus-trap-manager.ts | 63 + src/cdk/a11y/focus-trap/focus-trap.ts | 29 +- src/cdk/a11y/focus-trap/polyfill.ts | 40 + .../interactivity-checker.spec.ts | 2 +- .../a11y/key-manager/list-key-manager.spec.ts | 4 +- src/cdk/a11y/public-api.ts | 4 + src/cdk/clipboard/BUILD.bazel | 1 - src/cdk/clipboard/clipboard-module.ts | 2 - src/cdk/clipboard/copy-to-clipboard.spec.ts | 33 +- src/cdk/clipboard/copy-to-clipboard.ts | 34 +- src/cdk/collections/array-data-source.ts | 4 +- src/cdk/drag-drop/directives/config.ts | 54 + src/cdk/drag-drop/directives/drag.spec.ts | 274 ++- src/cdk/drag-drop/directives/drag.ts | 70 +- src/cdk/drag-drop/directives/drop-list.ts | 51 +- src/cdk/drag-drop/drag-ref.ts | 61 +- src/cdk/drag-drop/drop-list-ref.ts | 196 +- src/cdk/drag-drop/public-api.ts | 1 + .../fullscreen-overlay-container.spec.ts | 20 +- .../overlay/fullscreen-overlay-container.ts | 11 +- src/cdk/overlay/overlay-container.spec.ts | 30 +- src/cdk/overlay/overlay-container.ts | 53 +- src/cdk/overlay/overlay-ref.ts | 8 +- src/cdk/overlay/overlay.spec.ts | 23 + .../flexible-connected-position-strategy.ts | 11 +- .../position/global-position-strategy.spec.ts | 72 +- .../position/global-position-strategy.ts | 13 +- .../scroll/reposition-scroll-strategy.spec.ts | 2 +- src/cdk/portal/portal.spec.ts | 37 +- src/cdk/schematics/BUILD.bazel | 6 +- src/cdk/schematics/index.ts | 4 + src/cdk/schematics/ng-add/index.spec.ts | 4 +- src/cdk/schematics/ng-add/index.ts | 28 +- src/cdk/schematics/utils/ast.ts | 10 +- src/cdk/schematics/utils/build-component.ts | 12 +- src/cdk/schematics/utils/index.ts | 1 - .../utils/vendored-ast-utils/index.ts | 575 ++++++ .../utils/version-agnostic-typescript.ts | 47 - src/cdk/scrolling/scrollable.ts | 9 +- src/cdk/scrolling/virtual-for-of.ts | 14 +- .../scrolling/virtual-scroll-viewport.spec.ts | 21 +- src/cdk/scrolling/virtual-scroll-viewport.ts | 34 +- src/cdk/stepper/BUILD.bazel | 1 - src/cdk/stepper/stepper-module.ts | 3 +- src/cdk/table/BUILD.bazel | 1 - src/cdk/table/table-module.ts | 2 - src/cdk/table/table.ts | 11 +- src/cdk/testing/component-harness.ts | 11 +- src/cdk/testing/private/e2e/actions.ts | 2 +- .../testing/protractor/protractor-element.ts | 4 + src/cdk/testing/test-element.ts | 3 + src/cdk/testing/test-harnesses.md | 30 +- .../testbed/fake-events/event-objects.ts | 26 +- .../testbed/task-state-zone-interceptor.ts | 9 +- src/cdk/testing/testbed/unit-test-element.ts | 5 + src/cdk/testing/testbed/zone-types.d.ts | 12 +- src/cdk/testing/tests/BUILD.bazel | 1 + src/cdk/testing/tests/protractor.e2e.spec.ts | 8 + src/cdk/testing/tests/testbed.spec.ts | 8 + src/cdk/text-field/autosize.ts | 12 +- src/cdk/tree/BUILD.bazel | 1 - src/cdk/tree/control/nested-tree-control.ts | 4 +- src/cdk/tree/nested-node.ts | 4 +- src/cdk/tree/tree-module.ts | 2 - src/cdk/tree/tree.ts | 13 +- ...dk-drag-drop-custom-placeholder-example.ts | 3 +- .../cdk-drag-drop-custom-preview-example.ts | 4 + .../cdk-drag-drop-sorting-example.ts | 3 +- .../cdk-platform-overview-example.html | 1 + ...cdk-custom-stepper-without-form-example.ts | 6 - .../cdk-tree-flat/cdk-tree-flat-example.ts | 2 +- .../cdk-tree-nested-example.ts | 2 +- src/components-examples/example-data.ts | 4 +- .../mdc-form-field/BUILD.bazel | 32 + .../mdc-form-field/index.ts | 31 + .../example-tel-input-example.css | 21 + .../example-tel-input-example.html | 10 + .../form-field-custom-control-example.css | 1 + .../form-field-custom-control-example.html | 6 + .../form-field-custom-control-example.ts | 155 ++ .../popover-edit/BUILD.bazel | 3 + .../popover-edit/index.ts | 6 + .../popover-edit-mat-table-example.css | 2 +- .../popover-edit-mat-table-example.html | 89 +- .../popover-edit-mat-table-example.ts | 92 +- .../popover-edit-tab-out-mat-table-example.ts | 3 +- .../autocomplete-display-example.html | 8 +- .../autocomplete-display-example.ts | 2 +- .../datepicker-custom-icon-example.html | 3 +- .../datepicker-date-class-example.html | 3 +- .../datepicker-disabled-example.html | 9 +- .../datepicker-events-example.html | 3 +- .../datepicker-filter-example.html | 3 +- .../datepicker-formats-example.html | 3 +- .../datepicker-locale-example.html | 3 +- .../datepicker-min-max-example.html | 3 +- .../datepicker-moment-example.html | 3 +- .../datepicker-overview-example.html | 3 +- .../datepicker-start-view-example.html | 3 +- .../datepicker-touch-example.html | 3 +- .../datepicker-value-example.html | 9 +- .../datepicker-views-selection-example.html | 3 +- .../dialog-overview-example-dialog.html | 1 + .../dialog-overview-example.html | 3 +- .../expansion-expand-collapse-all-example.css | 4 + ...expansion-expand-collapse-all-example.html | 12 +- .../expansion-overview-example.css | 4 +- .../expansion-overview-example.html | 6 +- .../expansion-steps-example.css | 4 +- .../expansion-steps-example.html | 12 +- .../form-field-custom-control-example.html | 5 +- .../form-field-error-example.css | 9 +- .../form-field-error-example.html | 5 +- .../form-field-error-example.ts | 8 +- .../form-field-hint-example.css | 9 +- .../form-field-hint-example.html | 10 +- .../form-field-label-example.css | 9 +- .../form-field-label-example.html | 45 +- .../form-field-label-example.ts | 8 +- .../form-field-overview-example.css | 9 +- .../form-field-overview-example.html | 21 +- .../form-field-prefix-suffix-example.css | 9 +- .../form-field-prefix-suffix-example.html | 10 +- .../form-field-theming-example.css | 9 +- .../form-field-theming-example.html | 13 +- .../form-field-theming-example.ts | 10 +- .../input-clearable-example.html | 3 +- .../input-error-state-matcher-example.html | 5 +- .../input-errors/input-errors-example.html | 3 +- .../input/input-form/input-form-example.html | 24 +- .../input/input-hint/input-hint-example.html | 5 +- .../input-overview-example.html | 6 +- .../input-prefix-suffix-example.html | 5 +- .../material/list/index.ts | 3 + .../list-single-selection-example.css | 1 + .../list-single-selection-example.html | 9 + .../list-single-selection-example.ts | 13 + .../paginator-configurable-example.css | 2 +- .../paginator-configurable-example.html | 21 +- .../paginator-configurable-example.ts | 4 +- .../material/select/index.ts | 6 + .../select-custom-trigger-example.html | 3 +- .../select/select-form/select-form-example.ts | 4 +- .../select-hint-error-example.ts | 4 +- .../select-initial-value-example.css | 1 + .../select-initial-value-example.html | 20 + .../select-initial-value-example.ts | 38 + .../select-optgroup-example.ts | 4 +- .../select-overview-example.ts | 2 +- .../select-reactive-form-example.css | 1 + .../select-reactive-form-example.html | 24 + .../select-reactive-form-example.ts | 43 + .../sidenav-fixed/sidenav-fixed-example.html | 8 +- .../slider-configurable-example.css | 12 +- .../slider-configurable-example.html | 19 +- .../slider-configurable-example.ts | 6 +- .../snack-bar-component-example.css | 4 +- .../snack-bar-component-example.html | 2 +- .../snack-bar-overview-example.html | 8 +- .../snack-bar-position-example.css | 4 +- .../snack-bar-position-example.html | 8 +- .../snack-bar-position-example.ts | 3 +- .../stepper-editable-example.css | 8 +- .../stepper-editable-example.html | 9 +- .../stepper-errors/stepper-errors-example.css | 3 + .../stepper-errors-example.html | 7 +- .../stepper-label-position-bottom-example.css | 4 +- ...stepper-label-position-bottom-example.html | 65 +- .../stepper-optional-example.css | 8 +- .../stepper-optional-example.html | 7 +- .../stepper-overview-example.css | 8 +- .../stepper-overview-example.html | 7 +- .../stepper-states/stepper-states-example.css | 7 + .../stepper-states-example.html | 7 +- .../stepper-vertical-example.css | 8 +- .../stepper-vertical-example.html | 7 +- .../table-expandable-rows-example.css | 2 +- .../table-filtering-example.html | 3 +- .../table-filtering-example.ts | 3 +- .../table-multiple-header-footer-example.ts | 5 - .../table-overview-example.html | 3 +- .../table-overview/table-overview-example.ts | 3 +- .../tab-group-dynamic-example.html | 10 +- .../tooltip-auto-hide-example.html | 3 +- .../tooltip-delay/tooltip-delay-example.css | 6 +- .../tooltip-delay/tooltip-delay-example.html | 19 +- .../tooltip-message-example.html | 3 +- .../tooltip-position-example.html | 3 +- .../tree-checklist/tree-checklist-example.css | 3 + .../tree-checklist-example.html | 3 +- .../tree-flat-overview-example.ts | 4 +- .../tree-nested-overview-example.ts | 4 +- src/dev-app/BUILD.bazel | 20 +- src/dev-app/dev-app/dev-app-layout.ts | 1 + src/dev-app/dev-app/routes.ts | 1 + src/dev-app/focus-trap/BUILD.bazel | 1 + src/dev-app/focus-trap/focus-trap-demo.html | 68 +- src/dev-app/focus-trap/focus-trap-demo.ts | 79 +- src/dev-app/index.html | 10 + src/dev-app/list/list-demo.html | 18 + src/dev-app/list/list-demo.ts | 2 + src/dev-app/mdc-chips/mdc-chips-demo.html | 2 +- src/dev-app/mdc-input/BUILD.bazel | 35 + .../mdc-input/mdc-input-demo-module.ts | 48 + src/dev-app/mdc-input/mdc-input-demo.html | 672 +++++++ src/dev-app/mdc-input/mdc-input-demo.scss | 45 + src/dev-app/mdc-input/mdc-input-demo.ts | 105 ++ .../mdc-slide-toggle-demo.html | 4 + src/dev-app/platform/platform-demo.html | 17 +- src/dev-app/popover-edit/popover-edit-demo.ts | 18 +- src/dev-app/select/select-demo.html | 2 +- src/dev-app/system-config-tmpl.js | 156 -- src/e2e-app/radio/radio-e2e.html | 2 +- src/google-maps/google-map/google-map.spec.ts | 26 +- src/google-maps/google-map/google-map.ts | 39 +- src/google-maps/map-event-manager.ts | 7 +- .../map-info-window/map-info-window.ts | 32 +- src/google-maps/map-marker/map-marker.ts | 40 +- src/google-maps/map-polyline/map-polyline.ts | 36 +- src/material-experimental/BUILD.bazel | 6 +- src/material-experimental/config.bzl | 10 +- .../mdc-autocomplete/module.ts | 3 +- .../mdc-button/_mdc-button.scss | 10 +- .../mdc-button/button-base.ts | 21 +- .../mdc-button/button.scss | 29 +- .../mdc-button/button.ts | 7 - src/material-experimental/mdc-button/fab.scss | 6 +- src/material-experimental/mdc-button/fab.ts | 7 - .../mdc-button/icon-button.scss | 2 +- .../mdc-button/icon-button.ts | 7 - .../mdc-button/module.ts | 3 +- .../mdc-card/_mdc-card.scss | 4 +- src/material-experimental/mdc-card/card.scss | 4 +- .../mdc-checkbox/_mdc-checkbox.scss | 10 +- .../mdc-checkbox/checkbox.scss | 8 +- .../mdc-checkbox/checkbox.ts | 6 +- .../mdc-chips/BUILD.bazel | 1 + .../mdc-chips/_mdc-chips.scss | 4 +- .../mdc-chips/chip-grid.spec.ts | 62 +- .../mdc-chips/chip-grid.ts | 21 +- .../mdc-chips/chip-icons.ts | 16 +- .../mdc-chips/chip-input.spec.ts | 14 +- .../mdc-chips/chip-input.ts | 1 + .../mdc-chips/chip-listbox.ts | 1 - .../mdc-chips/chip-option.spec.ts | 18 + .../mdc-chips/chip-option.ts | 18 +- .../mdc-chips/chip-remove.spec.ts | 74 +- .../mdc-chips/chip-row.ts | 6 - .../mdc-chips/chip-set.ts | 3 +- .../mdc-chips/chip.spec.ts | 70 +- src/material-experimental/mdc-chips/chip.ts | 46 +- .../mdc-chips/chips.scss | 23 +- .../mdc-form-field/BUILD.bazel | 112 ++ .../mdc-form-field/README.md | 1 + .../_form-field-bottom-line.scss | 36 + .../mdc-form-field/_form-field-sizing.scss | 37 + .../mdc-form-field/_form-field-subscript.scss | 67 + .../mdc-form-field/_mdc-form-field.scss | 40 + .../_mdc-text-field-structure-overrides.scss | 106 ++ .../_mdc-text-field-textarea-overrides.scss | 51 + ...mdc-text-field-theme-variable-refresh.scss | 63 + .../mdc-form-field/directives/error.ts | 24 + .../directives/floating-label.ts | 45 + .../mdc-form-field/directives/hint.ts | 30 + .../mdc-form-field/directives/label.ts | 16 + .../mdc-form-field/directives/line-ripple.ts | 34 + .../directives/notched-outline.html | 5 + .../directives/notched-outline.ts | 76 + .../mdc-form-field/directives/prefix.ts | 15 + .../mdc-form-field/directives/suffix.ts | 15 + .../mdc-form-field/form-field.e2e.spec.ts | 0 .../mdc-form-field/form-field.html | 69 + .../mdc-form-field/form-field.scss | 54 + .../mdc-form-field/form-field.ts | 504 +++++ .../{mdc-helpers => mdc-form-field}/index.ts | 0 .../mdc-form-field/module.ts | 51 + .../mdc-form-field/public-api.ts | 21 + .../mdc-form-field/testing/BUILD.bazel | 59 + .../testing/form-field-harness.spec.ts | 38 + .../testing/form-field-harness.ts | 230 +++ .../mdc-form-field/testing/index.ts | 9 + .../mdc-form-field/testing/public-api.ts | 15 + .../mdc-helpers/BUILD.bazel | 120 +- .../mdc-helpers/_mdc-helpers.scss | 8 +- .../mdc-helpers/public-api.ts | 17 - .../mdc-input/BUILD.bazel | 88 + src/material-experimental/mdc-input/README.md | 1 + .../mdc-input/_mdc-input.scss | 9 + src/material-experimental/mdc-input/index.ts | 9 + .../mdc-input/input.e2e.spec.ts | 0 .../mdc-input/input.spec.ts | 1603 ++++++++++++++++ src/material-experimental/mdc-input/input.ts | 46 + src/material-experimental/mdc-input/module.ts | 20 + .../mdc-input/public-api.ts | 11 + .../mdc-input/testing/BUILD.bazel | 48 + .../mdc-input/testing/index.ts | 9 + .../mdc-input/testing/input-harness.spec.ts | 7 + .../mdc-input/testing/public-api.ts | 9 + .../mdc-menu/_mdc-menu.scss | 10 +- .../mdc-menu/menu-item.ts | 6 +- src/material-experimental/mdc-menu/menu.scss | 10 +- src/material-experimental/mdc-menu/menu.ts | 4 - .../mdc-progress-bar/_mdc-progress-bar.scss | 4 +- .../mdc-progress-bar/progress-bar.scss | 2 +- .../mdc-radio/radio.scss | 4 +- .../mdc-select/module.ts | 3 +- .../mdc-sidenav/module.ts | 3 +- .../mdc-slide-toggle/_mdc-slide-toggle.scss | 15 +- .../mdc-slide-toggle/slide-toggle.html | 6 +- .../mdc-slide-toggle/slide-toggle.scss | 12 +- .../mdc-slide-toggle/slide-toggle.spec.ts | 22 + .../mdc-slide-toggle/slide-toggle.ts | 37 +- .../mdc-slider/_mdc-slider.scss | 2 +- .../mdc-slider/slider.scss | 4 +- .../mdc-snackbar/BUILD.bazel | 6 +- .../mdc-snackbar/module.ts | 3 +- .../mdc-table/BUILD.bazel | 1 - .../mdc-table/_mdc-table.scss | 2 +- src/material-experimental/mdc-table/cell.ts | 1 - src/material-experimental/mdc-table/module.ts | 3 +- .../mdc-table/table.scss | 2 +- src/material-experimental/mdc-table/table.ts | 3 - .../mdc-tabs/BUILD.bazel | 11 - .../mdc-tabs/_mdc-tabs.scss | 15 +- .../mdc-tabs/_tabs-common.scss | 6 +- .../mdc-tabs/tab-group.scss | 4 +- .../mdc-tabs/tab-group.ts | 3 - .../mdc-tabs/tab-header.html | 4 +- .../mdc-tabs/tab-header.scss | 2 +- .../mdc-tabs/tab-header.spec.ts | 11 + .../mdc-tabs/tab-header.ts | 3 +- .../mdc-tabs/tab-label-wrapper.ts | 1 - .../mdc-tabs/tab-nav-bar/tab-link.scss | 2 +- .../mdc-tabs/tab-nav-bar/tab-nav-bar.html | 4 +- .../mdc-tabs/tab-nav-bar/tab-nav-bar.scss | 2 +- .../mdc-tabs/tab-nav-bar/tab-nav-bar.ts | 6 +- src/material-experimental/mdc-tabs/tab.ts | 3 - .../mdc-theming/BUILD.bazel | 10 + .../mdc-theming/_all-theme.scss | 4 + .../mdc-typography/BUILD.bazel | 7 + .../mdc-typography/_all-typography.scss | 4 + .../mdc_require_config.js | 2 +- .../popover-edit/_popover-edit.scss | 23 +- .../popover-edit/lens-directives.ts | 7 +- .../popover-edit/popover-edit-module.ts | 2 - .../popover-edit/popover-edit.spec.ts | 33 +- .../popover-edit/table-directives.ts | 5 +- src/material/BUILD.bazel | 2 +- src/material/autocomplete/autocomplete.scss | 2 +- src/material/badge/_badge-theme.scss | 2 +- src/material/bottom-sheet/BUILD.bazel | 1 - .../bottom-sheet/bottom-sheet-container.scss | 2 +- .../bottom-sheet/bottom-sheet-module.ts | 2 - src/material/button-toggle/button-toggle.scss | 12 +- src/material/button/BUILD.bazel | 1 - src/material/button/button-module.ts | 2 - src/material/button/button.scss | 6 +- src/material/button/button.ts | 3 - src/material/card/card.md | 26 +- src/material/card/card.scss | 2 +- src/material/checkbox/BUILD.bazel | 1 - src/material/checkbox/_checkbox-theme.scss | 20 - src/material/checkbox/checkbox-module.ts | 3 +- src/material/checkbox/checkbox.scss | 23 +- src/material/checkbox/checkbox.ts | 6 +- src/material/chips/chip-input.spec.ts | 14 +- src/material/chips/chip-input.ts | 1 + src/material/chips/chip-remove.spec.ts | 8 +- src/material/chips/chip.spec.ts | 55 +- src/material/chips/chip.ts | 22 +- src/material/chips/chips.scss | 2 +- src/material/config.bzl | 2 + src/material/core/label/label-options.ts | 19 +- src/material/core/option/option.scss | 4 +- src/material/core/ripple/_ripple.scss | 11 +- src/material/core/ripple/ripple-renderer.ts | 9 +- src/material/core/ripple/ripple.spec.ts | 2 +- src/material/datepicker/calendar-body.scss | 4 +- src/material/datepicker/calendar.spec.ts | 39 + src/material/datepicker/calendar.ts | 12 +- .../datepicker/datepicker-content.scss | 6 +- src/material/datepicker/datepicker-input.ts | 3 +- src/material/datepicker/datepicker-toggle.ts | 2 +- src/material/datepicker/datepicker.md | 70 +- src/material/datepicker/datepicker.spec.ts | 56 +- src/material/datepicker/datepicker.ts | 106 +- src/material/datepicker/month-view.ts | 15 +- src/material/datepicker/multi-year-view.ts | 15 +- src/material/datepicker/year-view.ts | 15 +- src/material/dialog/BUILD.bazel | 1 - src/material/dialog/dialog-module.ts | 2 - src/material/dialog/dialog.scss | 2 +- src/material/divider/BUILD.bazel | 1 - src/material/divider/divider-module.ts | 3 +- src/material/expansion/accordion.ts | 1 - .../expansion/expansion-panel-header.ts | 4 +- src/material/expansion/expansion-panel.scss | 2 +- src/material/expansion/expansion-panel.ts | 15 + src/material/expansion/expansion.spec.ts | 46 + src/material/form-field/form-field-fill.scss | 4 +- src/material/form-field/form-field-input.scss | 2 +- .../form-field/form-field-legacy.scss | 6 +- .../form-field/form-field-outline.scss | 3 +- .../form-field/form-field-standard.scss | 6 +- src/material/form-field/form-field.scss | 2 +- src/material/form-field/form-field.ts | 33 +- .../testing/form-field-harness.spec.ts | 13 +- .../form-field/testing/form-field-harness.ts | 13 +- .../form-field/testing/shared.spec.ts | 72 +- src/material/grid-list/grid-list.ts | 3 + src/material/grid-list/grid-tile.ts | 4 + src/material/grid-list/public-api.ts | 2 + src/material/grid-list/testing/BUILD.bazel | 52 + .../testing/grid-list-harness-filters.ts | 20 + .../testing/grid-list-harness.spec.ts | 8 + .../grid-list/testing/grid-list-harness.ts | 76 + .../grid-list/testing/grid-tile-harness.ts | 77 + src/material/grid-list/testing/index.ts | 9 + src/material/grid-list/testing/public-api.ts | 11 + src/material/grid-list/testing/shared.spec.ts | 194 ++ src/material/grid-list/tile-coordinator.ts | 18 +- src/material/icon/icon.md | 33 +- src/material/icon/testing/BUILD.bazel | 25 + .../icon/testing/fake-icon-registry.ts | 101 + src/material/icon/testing/index.ts | 9 + src/material/icon/testing/public-api.ts | 9 + src/material/input/BUILD.bazel | 1 - src/material/input/autosize.ts | 5 - src/material/input/input-module.ts | 3 - src/material/input/input.spec.ts | 14 +- src/material/input/input.ts | 10 +- src/material/input/testing/input-harness.ts | 5 +- src/material/input/testing/shared.spec.ts | 60 +- src/material/list/_list-theme.scss | 6 + src/material/list/list-option.html | 1 + src/material/list/list.scss | 8 +- src/material/list/list.spec.ts | 48 +- src/material/list/list.ts | 33 +- src/material/list/selection-list.spec.ts | 88 +- src/material/list/selection-list.ts | 33 +- src/material/menu/menu-item.ts | 3 +- src/material/menu/menu-trigger.ts | 5 +- src/material/menu/menu.scss | 4 +- src/material/menu/menu.ts | 6 +- src/material/paginator/paginator.spec.ts | 138 +- src/material/paginator/paginator.ts | 47 +- src/material/progress-bar/progress-bar.scss | 6 +- .../progress-spinner/progress-spinner.ts | 4 - src/material/radio/BUILD.bazel | 1 - src/material/radio/radio-module.ts | 3 +- src/material/radio/radio.scss | 4 +- src/material/schematics/BUILD.bazel | 6 +- src/material/schematics/ng-add/index.spec.ts | 32 +- src/material/schematics/ng-add/index.ts | 5 +- .../schematics/ng-add/setup-project.ts | 2 +- .../schematics/ng-add/theming/theming.ts | 4 +- .../schematics/ng-add/version-names.ts | 13 +- .../hammer-gestures-rule.ts | 4 +- src/material/select/select.scss | 2 +- src/material/select/select.spec.ts | 22 +- src/material/select/select.ts | 31 +- src/material/select/testing/shared.spec.ts | 4 - src/material/sidenav/drawer.scss | 6 +- src/material/sidenav/drawer.spec.ts | 20 +- src/material/sidenav/drawer.ts | 38 +- src/material/sidenav/sidenav.ts | 5 - src/material/slide-toggle/BUILD.bazel | 1 + src/material/slide-toggle/slide-toggle.scss | 35 +- .../slide-toggle/slide-toggle.spec.ts | 15 + src/material/slide-toggle/slide-toggle.ts | 9 +- src/material/slider/slider.scss | 8 +- src/material/slider/slider.ts | 29 +- .../snack-bar/snack-bar-container.scss | 2 +- src/material/sort/sort-header.scss | 6 +- src/material/stepper/stepper.ts | 13 +- src/material/table/BUILD.bazel | 1 - src/material/table/cell.ts | 1 - src/material/table/table-module.ts | 2 - src/material/table/table.ts | 3 - src/material/table/testing/row-harness.ts | 33 + src/material/table/testing/shared.spec.ts | 21 + src/material/table/testing/table-harness.ts | 34 +- src/material/tabs/_tabs-common.scss | 12 +- src/material/tabs/paginated-tab-header.ts | 12 +- src/material/tabs/tab-group.ts | 10 +- src/material/tabs/tab-header.html | 4 +- src/material/tabs/tab-header.spec.ts | 11 + src/material/tabs/tab-header.ts | 3 +- .../tabs/tab-nav-bar/tab-nav-bar.html | 4 +- src/material/tabs/tab-nav-bar/tab-nav-bar.ts | 9 +- src/material/toolbar/toolbar.scss | 2 +- src/material/tooltip/tooltip.scss | 2 +- src/material/tree/BUILD.bazel | 1 - src/material/tree/padding.ts | 3 - src/material/tree/toggle.ts | 3 - src/material/tree/tree-module.ts | 3 +- src/material/tree/tree.scss | 1 - src/material/tree/tree.spec.ts | 2 +- src/universal-app/BUILD.bazel | 2 +- .../kitchen-sink-mdc/kitchen-sink-mdc.html | 45 +- .../kitchen-sink-mdc/kitchen-sink-mdc.ts | 4 + .../kitchen-sink/kitchen-sink.html | 14 +- src/universal-app/prerender.ts | 9 +- src/youtube-player/youtube-player.spec.ts | 23 +- src/youtube-player/youtube-player.ts | 135 +- test/BUILD.bazel | 12 + test/bazel-karma-local-config.js | 14 +- test/karma-system-config.js | 240 --- test/karma.conf.js | 8 +- tools/BUILD.bazel | 5 + tools/bazel/BUILD.bazel | 2 +- .../angular_bazel_rules_nodejs_1.0.0.patch | 77 + tools/bazel/expand_template.bzl | 6 +- tools/bazel/postinstall-patches.js | 19 +- tools/bazel/sass_worker_async.patch | 17 + tools/create-system-config.bzl | 42 + tools/defaults.bzl | 7 +- tools/dev-server/BUILD.bazel | 2 +- tools/dev-server/dev-server.ts | 1 + tools/dgeni/BUILD.bazel | 2 +- tools/dgeni/common/class-inheritance.ts | 86 +- tools/dgeni/docs-package.ts | 15 +- tools/dgeni/processors/categorizer.ts | 17 +- tools/dgeni/processors/docs-private-filter.ts | 2 +- .../processors/merge-inherited-properties.ts | 23 +- tools/example-module/BUILD.bazel | 2 +- tools/example-module/bazel-bin.ts | 1 + tools/example-module/example-module.template | 8 +- .../example-module/generate-example-module.ts | 8 +- tools/gulp/packages.ts | 11 - tools/gulp/tasks/unit-test.ts | 76 +- tools/highlight-files/BUILD.bazel | 2 +- tools/markdown-to-html/BUILD.bazel | 2 +- tools/package-docs-content/BUILD.bazel | 2 +- .../package-tools/gulp/build-scss-pipeline.ts | 4 +- tools/public_api_guard/BUILD.bazel | 13 +- tools/public_api_guard/cdk/a11y.d.ts | 57 +- tools/public_api_guard/cdk/accordion.d.ts | 13 +- tools/public_api_guard/cdk/bidi.d.ts | 7 +- tools/public_api_guard/cdk/clipboard.d.ts | 7 +- tools/public_api_guard/cdk/coercion.d.ts | 2 +- tools/public_api_guard/cdk/collections.d.ts | 2 +- tools/public_api_guard/cdk/drag-drop.d.ts | 72 +- tools/public_api_guard/cdk/observers.d.ts | 8 +- tools/public_api_guard/cdk/overlay.d.ts | 48 +- tools/public_api_guard/cdk/portal.d.ts | 13 +- tools/public_api_guard/cdk/scrolling.d.ts | 29 +- tools/public_api_guard/cdk/stepper.d.ts | 33 +- tools/public_api_guard/cdk/table.d.ts | 32 +- tools/public_api_guard/cdk/testing.d.ts | 1 + .../cdk/testing/protractor.d.ts | 1 + .../public_api_guard/cdk/testing/testbed.d.ts | 1 + tools/public_api_guard/cdk/text-field.d.ts | 13 +- tools/public_api_guard/cdk/tree.d.ts | 31 +- .../google-maps/google-maps.d.ts | 53 +- .../material/autocomplete.d.ts | 20 +- tools/public_api_guard/material/badge.d.ts | 14 +- .../material/bottom-sheet.d.ts | 5 +- .../material/bottom-sheet/testing.d.ts | 9 + .../material/button-toggle.d.ts | 32 +- tools/public_api_guard/material/button.d.ts | 8 +- tools/public_api_guard/material/card.d.ts | 2 +- tools/public_api_guard/material/checkbox.d.ts | 18 +- tools/public_api_guard/material/chips.d.ts | 79 +- tools/public_api_guard/material/core.d.ts | 33 +- .../public_api_guard/material/datepicker.d.ts | 142 +- tools/public_api_guard/material/dialog.d.ts | 12 +- tools/public_api_guard/material/divider.d.ts | 10 +- .../public_api_guard/material/expansion.d.ts | 21 +- .../public_api_guard/material/form-field.d.ts | 30 +- .../material/form-field/testing.d.ts | 1 - .../material/form-field/testing/control.d.ts | 2 + .../public_api_guard/material/grid-list.d.ts | 19 +- .../material/grid-list/testing.d.ts | 30 + tools/public_api_guard/material/icon.d.ts | 11 +- .../material/icon/testing.d.ts | 24 + tools/public_api_guard/material/input.d.ts | 45 +- tools/public_api_guard/material/list.d.ts | 37 +- tools/public_api_guard/material/menu.d.ts | 37 +- .../public_api_guard/material/paginator.d.ts | 31 +- .../material/progress-bar.d.ts | 8 +- .../material/progress-spinner.d.ts | 26 +- tools/public_api_guard/material/radio.d.ts | 44 +- tools/public_api_guard/material/select.d.ts | 47 +- tools/public_api_guard/material/sidenav.d.ts | 57 +- .../material/slide-toggle.d.ts | 10 +- tools/public_api_guard/material/slider.d.ts | 50 +- .../public_api_guard/material/snack-bar.d.ts | 5 +- tools/public_api_guard/material/sort.d.ts | 13 +- tools/public_api_guard/material/stepper.d.ts | 24 +- tools/public_api_guard/material/table.d.ts | 24 +- .../material/table/testing.d.ts | 7 + tools/public_api_guard/material/tabs.d.ts | 69 +- tools/public_api_guard/material/toolbar.d.ts | 2 +- tools/public_api_guard/material/tooltip.d.ts | 16 +- tools/public_api_guard/material/tree.d.ts | 26 +- .../youtube-player/youtube-player.d.ts | 17 +- tools/release/base-release-task.ts | 41 +- tools/release/publish-release.ts | 2 +- tools/release/stage-release.ts | 2 +- tools/system-config-tmpl.js | 181 ++ .../system-rxjs-operators.js | 0 tools/tslint-rules/coercionTypesRule.ts | 140 +- .../noCrossEntryPointRelativeImportsRule.ts | 10 +- .../tslint-rules/noImportExportSpacingRule.ts | 37 + tools/tslint-rules/noImportSpacingRule.ts | 35 - tools/tslint-rules/noNestedTernaryRule.ts | 37 + tools/tslint-rules/rxjsImportsRule.ts | 23 - tsconfig.json | 1 + tslint.json | 9 +- yarn.lock | 1625 +++++++++-------- 647 files changed, 13997 insertions(+), 3988 deletions(-) create mode 100644 scripts/check-entry-point-setup.js create mode 100644 scripts/run-component-tests.js create mode 100644 src/cdk/a11y/focus-trap/configurable-focus-trap-config.ts create mode 100644 src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts create mode 100644 src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts create mode 100644 src/cdk/a11y/focus-trap/configurable-focus-trap.ts create mode 100644 src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts create mode 100644 src/cdk/a11y/focus-trap/event-listener-inert-strategy.ts create mode 100644 src/cdk/a11y/focus-trap/focus-trap-inert-strategy.ts create mode 100644 src/cdk/a11y/focus-trap/focus-trap-manager.spec.ts create mode 100644 src/cdk/a11y/focus-trap/focus-trap-manager.ts create mode 100644 src/cdk/a11y/focus-trap/polyfill.ts create mode 100644 src/cdk/drag-drop/directives/config.ts create mode 100644 src/cdk/schematics/utils/vendored-ast-utils/index.ts delete mode 100644 src/cdk/schematics/utils/version-agnostic-typescript.ts create mode 100644 src/components-examples/material-experimental/mdc-form-field/BUILD.bazel create mode 100644 src/components-examples/material-experimental/mdc-form-field/index.ts create mode 100644 src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.css create mode 100644 src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.html create mode 100644 src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.css create mode 100644 src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.html create mode 100644 src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts create mode 100644 src/components-examples/material/list/list-single-selection/list-single-selection-example.css create mode 100644 src/components-examples/material/list/list-single-selection/list-single-selection-example.html create mode 100644 src/components-examples/material/list/list-single-selection/list-single-selection-example.ts create mode 100644 src/components-examples/material/select/select-initial-value/select-initial-value-example.css create mode 100644 src/components-examples/material/select/select-initial-value/select-initial-value-example.html create mode 100644 src/components-examples/material/select/select-initial-value/select-initial-value-example.ts create mode 100644 src/components-examples/material/select/select-reactive-form/select-reactive-form-example.css create mode 100644 src/components-examples/material/select/select-reactive-form/select-reactive-form-example.html create mode 100644 src/components-examples/material/select/select-reactive-form/select-reactive-form-example.ts create mode 100644 src/dev-app/mdc-input/BUILD.bazel create mode 100644 src/dev-app/mdc-input/mdc-input-demo-module.ts create mode 100644 src/dev-app/mdc-input/mdc-input-demo.html create mode 100644 src/dev-app/mdc-input/mdc-input-demo.scss create mode 100644 src/dev-app/mdc-input/mdc-input-demo.ts delete mode 100644 src/dev-app/system-config-tmpl.js create mode 100644 src/material-experimental/mdc-form-field/BUILD.bazel create mode 100644 src/material-experimental/mdc-form-field/README.md create mode 100644 src/material-experimental/mdc-form-field/_form-field-bottom-line.scss create mode 100644 src/material-experimental/mdc-form-field/_form-field-sizing.scss create mode 100644 src/material-experimental/mdc-form-field/_form-field-subscript.scss create mode 100644 src/material-experimental/mdc-form-field/_mdc-form-field.scss create mode 100644 src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss create mode 100644 src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss create mode 100644 src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss create mode 100644 src/material-experimental/mdc-form-field/directives/error.ts create mode 100644 src/material-experimental/mdc-form-field/directives/floating-label.ts create mode 100644 src/material-experimental/mdc-form-field/directives/hint.ts create mode 100644 src/material-experimental/mdc-form-field/directives/label.ts create mode 100644 src/material-experimental/mdc-form-field/directives/line-ripple.ts create mode 100644 src/material-experimental/mdc-form-field/directives/notched-outline.html create mode 100644 src/material-experimental/mdc-form-field/directives/notched-outline.ts create mode 100644 src/material-experimental/mdc-form-field/directives/prefix.ts create mode 100644 src/material-experimental/mdc-form-field/directives/suffix.ts create mode 100644 src/material-experimental/mdc-form-field/form-field.e2e.spec.ts create mode 100644 src/material-experimental/mdc-form-field/form-field.html create mode 100644 src/material-experimental/mdc-form-field/form-field.scss create mode 100644 src/material-experimental/mdc-form-field/form-field.ts rename src/material-experimental/{mdc-helpers => mdc-form-field}/index.ts (100%) create mode 100644 src/material-experimental/mdc-form-field/module.ts create mode 100644 src/material-experimental/mdc-form-field/public-api.ts create mode 100644 src/material-experimental/mdc-form-field/testing/BUILD.bazel create mode 100644 src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts create mode 100644 src/material-experimental/mdc-form-field/testing/form-field-harness.ts create mode 100644 src/material-experimental/mdc-form-field/testing/index.ts create mode 100644 src/material-experimental/mdc-form-field/testing/public-api.ts delete mode 100644 src/material-experimental/mdc-helpers/public-api.ts create mode 100644 src/material-experimental/mdc-input/BUILD.bazel create mode 100644 src/material-experimental/mdc-input/README.md create mode 100644 src/material-experimental/mdc-input/_mdc-input.scss create mode 100644 src/material-experimental/mdc-input/index.ts create mode 100644 src/material-experimental/mdc-input/input.e2e.spec.ts create mode 100644 src/material-experimental/mdc-input/input.spec.ts create mode 100644 src/material-experimental/mdc-input/input.ts create mode 100644 src/material-experimental/mdc-input/module.ts create mode 100644 src/material-experimental/mdc-input/public-api.ts create mode 100644 src/material-experimental/mdc-input/testing/BUILD.bazel create mode 100644 src/material-experimental/mdc-input/testing/index.ts create mode 100644 src/material-experimental/mdc-input/testing/input-harness.spec.ts create mode 100644 src/material-experimental/mdc-input/testing/public-api.ts create mode 100644 src/material/grid-list/testing/BUILD.bazel create mode 100644 src/material/grid-list/testing/grid-list-harness-filters.ts create mode 100644 src/material/grid-list/testing/grid-list-harness.spec.ts create mode 100644 src/material/grid-list/testing/grid-list-harness.ts create mode 100644 src/material/grid-list/testing/grid-tile-harness.ts create mode 100644 src/material/grid-list/testing/index.ts create mode 100644 src/material/grid-list/testing/public-api.ts create mode 100644 src/material/grid-list/testing/shared.spec.ts create mode 100644 src/material/icon/testing/BUILD.bazel create mode 100644 src/material/icon/testing/fake-icon-registry.ts create mode 100644 src/material/icon/testing/index.ts create mode 100644 src/material/icon/testing/public-api.ts delete mode 100644 test/karma-system-config.js create mode 100644 tools/bazel/angular_bazel_rules_nodejs_1.0.0.patch create mode 100644 tools/bazel/sass_worker_async.patch create mode 100644 tools/create-system-config.bzl create mode 100644 tools/public_api_guard/material/bottom-sheet/testing.d.ts create mode 100644 tools/public_api_guard/material/form-field/testing/control.d.ts create mode 100644 tools/public_api_guard/material/grid-list/testing.d.ts create mode 100644 tools/public_api_guard/material/icon/testing.d.ts create mode 100644 tools/system-config-tmpl.js rename {src/dev-app => tools}/system-rxjs-operators.js (100%) create mode 100644 tools/tslint-rules/noImportExportSpacingRule.ts delete mode 100644 tools/tslint-rules/noImportSpacingRule.ts create mode 100644 tools/tslint-rules/noNestedTernaryRule.ts delete mode 100644 tools/tslint-rules/rxjsImportsRule.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 3bc43b93ca4e..a09a909491bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -205,7 +205,7 @@ jobs: - *yarn_install - *setup_bazel_binary - - run: bazel test src/... --build_tag_filters=e2e --test_tag_filters=e2e + - run: bazel test src/... --build_tag_filters=e2e --test_tag_filters=e2e --build_tests_only # ------------------------------------------------------------------------------------------ # Job that runs the unit tests on locally installed browsers (Chrome and Firefox headless). @@ -225,7 +225,7 @@ jobs: - *yarn_install - *setup_bazel_binary - - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e + - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only # ---------------------------------------------------------------------------- # Job that runs the unit tests on Browserstack. The browsers that will be used @@ -240,6 +240,7 @@ jobs: steps: - *checkout_code - *restore_cache + - *setup_bazel_ci_config - *yarn_download - *yarn_install @@ -261,6 +262,7 @@ jobs: steps: - *checkout_code - *restore_cache + - *setup_bazel_ci_config - *yarn_download - *yarn_install @@ -285,6 +287,12 @@ jobs: bazel build //:rollup_globals yarn check-rollup-globals $(bazel info bazel-bin)/rollup_globals.json + - run: + name: Checking entry-points configuration + command: | + bazel build //:entry_points_manifest + yarn check-entry-point-setup $(bazel info bazel-bin)/entry_points_manifest.json + - run: ./scripts/circleci/lint-bazel-files.sh - run: yarn ownerslint - run: yarn stylelint @@ -369,8 +377,9 @@ jobs: # to publish the build snapshots. In order to fix this, we unset the global option. - run: git config --global --unset "url.ssh://git@github.com.insteadof" - # TODO(devversion): Ideally the "build_release_packages" job should build all packages with - # Bazel, but for now we mix up the Gulp and bazel setup, so we need to build the package here. + # The components examples package is not a release package, but we publish it + # as part of this job to the docs-content repository. It's not contained in the + # attached release output, so we need to build it here. - run: bazel build src/components-examples:npm_package --config=release - run: ./scripts/circleci/publish-snapshots.sh @@ -428,7 +437,7 @@ jobs: - *yarn_install_loose_lockfile - *setup_bazel_binary - - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e + - run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only # ---------------------------------------------------------------------------- # Job that runs all Bazel tests against View Engine with the current Angular version @@ -449,8 +458,7 @@ jobs: - *setup_bazel_binary # Run project tests with NGC and View Engine. - - run: bazel build src/... --build_tag_filters=-docs-package,-e2e --config=view-engine - - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine + - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only # ---------------------------------------------------------------------------- # Job that runs all Bazel tests against View Engine from angular/angular#master. @@ -471,8 +479,7 @@ jobs: - *setup_bazel_binary # Run project tests with NGC and View Engine. - - run: bazel build src/... --build_tag_filters=-docs-package,-e2e --config=view-engine - - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine + - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only # ---------------------------------------------------------------------------- # Job that runs all Bazel tests against material-components-web@canary @@ -496,8 +503,7 @@ jobs: # Setup the components repository to use the MDC snapshot builds. # Run project tests with the MDC canary builds. - - run: bazel build src/... --build_tag_filters=-docs-package,-e2e - - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e + - run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only # ---------------------------------------------------------------------------------------- # Workflow definitions. A workflow usually groups multiple jobs together. This is useful if diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 80fc29e372d0..96e3fea241bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -96,7 +96,9 @@ /src/material-experimental/mdc-checkbox/** @mmalerba /src/material-experimental/mdc-chips/** @mmalerba /src/material-experimental/mdc-dialog/** @devversion +/src/material-experimental/mdc-form-field/** @devversion @mmalerba /src/material-experimental/mdc-helpers/** @mmalerba +/src/material-experimental/mdc-input/** @devversion @mmalerba /src/material-experimental/mdc-list/** @mmalerba /src/material-experimental/mdc-menu/** @crisbeto /src/material-experimental/mdc-select/** @crisbeto @@ -160,6 +162,7 @@ /src/dev-app/mdc-card/** @mmalerba /src/dev-app/mdc-checkbox/** @mmalerba /src/dev-app/mdc-chips/** @mmalerba +/src/dev-app/mdc-input/** @devversion @mmalerba /src/dev-app/mdc-menu/** @crisbeto /src/dev-app/mdc-progress-bar/** @crisbeto /src/dev-app/mdc-radio/** @mmalerba diff --git a/BUILD.bazel b/BUILD.bazel index 1340d7e00d17..09116fdde49f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,6 +1,10 @@ package(default_visibility = ["//visibility:public"]) load("//:rollup-globals.bzl", "ROLLUP_GLOBALS") +load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS") +load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_ENTRYPOINTS") +load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS") +load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_ENTRYPOINTS", "MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS") exports_files([ "LICENSE", @@ -12,3 +16,14 @@ genrule( outs = ["rollup_globals.json"], cmd = "echo '%s' > $@" % ROLLUP_GLOBALS, ) + +entryPoints = ["cdk/%s" % e for e in CDK_ENTRYPOINTS] + \ + ["cdk-experimental/%s" % e for e in CDK_EXPERIMENTAL_ENTRYPOINTS] + \ + ["material/%s" % e for e in MATERIAL_ENTRYPOINTS + MATERIAL_TESTING_ENTRYPOINTS] + \ + ["material-experimental/%s" % e for e in MATERIAL_EXPERIMENTAL_ENTRYPOINTS + MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS] + +genrule( + name = "entry_points_manifest", + outs = ["entry_points_manifest.json"], + cmd = "echo '%s' > $@" % entryPoints, +) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4893ccaf6397..b61e814f0eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +# 9.0.0-rc.9 "uranium-surfboard" (2020-01-31) + +### cdk + +| | | +| ---------- | --------------------- | +| bug fix | **a11y:** make cdk-high-contrast work w/ emulated view encapsulation ([#18152](https://github.com/angular/components/issues/18152)) ([aff21e8](https://github.com/angular/components/commit/aff21e8)) | + +### material + +| | | +| ---------- | --------------------- | +| bug fix | reduce amount of generated high contrast styles ([#18332](https://github.com/angular/components/issues/18332)) ([b119472](https://github.com/angular/components/commit/b119472)) | +| bug fix | **checkbox:** outline not visible when checked in high contrast mode ([#18048](https://github.com/angular/components/issues/18048)) ([75a7681](https://github.com/angular/components/commit/75a7681)) | +| bug fix | **chips:** set aria-required on chip input ([#18098](https://github.com/angular/components/issues/18098)) ([05d072a](https://github.com/angular/components/commit/05d072a)), closes [#18049](https://github.com/angular/components/issues/18049) | +| bug fix | **datepicker:** able to focus disabled datepicker toggle while disabled via click ([#18231](https://github.com/angular/components/issues/18231)) ([9086a4b](https://github.com/angular/components/commit/9086a4b)) | +| bug fix | **drag-drop:** error if next sibling is removed after drag sequence has started ([#18230](https://github.com/angular/components/issues/18230)) ([7a167a2](https://github.com/angular/components/commit/7a167a2)), closes [#18205](https://github.com/angular/components/issues/18205) | +| bug fix | **drag-drop:** not picking up initial disabled state of handle ([#16643](https://github.com/angular/components/issues/16643)) ([3b28d33](https://github.com/angular/components/commit/3b28d33)), closes [#16642](https://github.com/angular/components/issues/16642) | +| bug fix | **drag-drop:** prevent dragging selected text with the mouse ([#18103](https://github.com/angular/components/issues/18103)) ([fbbac37](https://github.com/angular/components/commit/fbbac37)) | +| bug fix | **expansion:** unable to toggle disabled panel via methods ([#18181](https://github.com/angular/components/issues/18181)) ([796db4d](https://github.com/angular/components/commit/796db4d)), closes [#18171](https://github.com/angular/components/issues/18171) | +| bug fix | **input:** input harness not matching `matNativeControl` ([#18221](https://github.com/angular/components/issues/18221)) ([f0d3a6b](https://github.com/angular/components/commit/f0d3a6b)) | +| bug fix | **overlay:** don't reset global overlay alignment when width is 100% and there's a maxWidth ([#17842](https://github.com/angular/components/issues/17842)) ([37d0191](https://github.com/angular/components/commit/37d0191)), closes [#17841](https://github.com/angular/components/issues/17841) | +| bug fix | **overlay:** only clear duplicate containers from different platform ([#17006](https://github.com/angular/components/issues/17006)) ([67d27fd](https://github.com/angular/components/commit/67d27fd)), closes [#16851](https://github.com/angular/components/issues/16851) | +| bug fix | **schematics:** global typography class always being added ([#18315](https://github.com/angular/components/issues/18315)) ([2b83a0a](https://github.com/angular/components/commit/2b83a0a)), closes [#17602](https://github.com/angular/components/issues/17602) [#16776](https://github.com/angular/components/issues/16776) | +| bug fix | **tabs:** better high contrast indication on supported browsers ([#18160](https://github.com/angular/components/issues/18160)) ([01b31de](https://github.com/angular/components/commit/01b31de)) | +| bug fix | **tabs:** don't start auto-scroll when right clicking on buttons ([#18033](https://github.com/angular/components/issues/18033)) ([ca9b204](https://github.com/angular/components/commit/ca9b204)) | + +### youtube-player + +| | | +| ---------- | --------------------- | +| bug fix | memory leak if player is destroyed before it is done initializing ([#18046](https://github.com/angular/components/issues/18046)) ([6b3a271](https://github.com/angular/components/commit/6b3a271)) | +| bug fix | not picking up static startSeconds and endSeconds ([#18214](https://github.com/angular/components/issues/18214)) ([8ea430f](https://github.com/angular/components/commit/8ea430f)), closes [#18212](https://github.com/angular/components/issues/18212) | + +### material-experimental + +| | | +| ---------- | --------------------- | +| bug fix | errors with latest MDC canary version ([#18173](https://github.com/angular/components/issues/18173)) ([5d13491](https://github.com/angular/components/commit/5d13491)) | +| bug fix | mdc-theming and mdc-typography files not published ([#18251](https://github.com/angular/components/issues/18251)) ([77a25eb](https://github.com/angular/components/commit/77a25eb)) | + + +# 9.0.0-rc.8 "stone-stallion" (2020-01-16) + +### material + +| | | +| ---------- | --------------------- | +| bug fix | **form-field:** undeprecated legacy and standard appearances ([#18144](https://github.com/angular/components/issues/18144)) ([9ea5694](https://github.com/angular/components/commit/9ea5694)) | + +### google-maps + +| | | +| ---------- | --------------------- | +| bug fix | sub-components throwing during server-side rendering ([#18059](https://github.com/angular/components/issues/18059)) ([a7f7e9b](https://github.com/angular/components/commit/a7f7e9b)) | + + +# 9.0.0-rc.7 "obsidian-elephant" (2020-01-08) + +### material + +| | | +| ---------- | --------------------- | +| bug fix | **chips:** avoid accidental form submits when pressing remove button ([#18095](https://github.com/angular/components/issues/18095)) ([3b4e496](https://github.com/angular/components/commit/3b4e496)) | +| bug fix | **datepicker:** not respecting form control updateOn: blur for invalid values ([#18063](https://github.com/angular/components/issues/18063)) ([61029c8](https://github.com/angular/components/commit/61029c8)), closes [#16461](https://github.com/angular/components/issues/16461) | +| bug fix | **datepicker:** re-render calendar when locale changes ([#18094](https://github.com/angular/components/issues/18094)) ([be17c25](https://github.com/angular/components/commit/be17c25)), closes [#18087](https://github.com/angular/components/issues/18087) | +| bug fix | **form-field:** delete top border from focus animation ([#17885](https://github.com/angular/components/issues/17885)) ([09dc459](https://github.com/angular/components/commit/09dc459)), closes [#17884](https://github.com/angular/components/issues/17884) | +| bug fix | **form-field:** error when focusing outline form field angular elements on IE/Edge ([#18062](https://github.com/angular/components/issues/18062)) ([f573072](https://github.com/angular/components/commit/f573072)), closes [#16095](https://github.com/angular/components/issues/16095) | +| bug fix | **scrolling:** update virtual scroll viewport size on resize ([#18058](https://github.com/angular/components/issues/18058)) ([c36466c](https://github.com/angular/components/commit/c36466c)), closes [#16802](https://github.com/angular/components/issues/16802) | +| bug fix | **sidenav:** move focus into sidenav in side mode if autoFocus enabled explicitly ([#17995](https://github.com/angular/components/issues/17995)) ([c9856ee](https://github.com/angular/components/commit/c9856ee)), closes [#17967](https://github.com/angular/components/issues/17967) | +| bug fix | **slide-toggle:** fix and simplify high contrast styles ([#18104](https://github.com/angular/components/issues/18104)) ([2fd862d](https://github.com/angular/components/commit/2fd862d)) | +| feature | **grid-list:** add test harness ([#18001](https://github.com/angular/components/issues/18001)) ([5e9ca28](https://github.com/angular/components/commit/5e9ca28)) | +| feature | **table:** add test harness ([#17799](https://github.com/angular/components/issues/17799)) ([a30094b](https://github.com/angular/components/commit/a30094b)) | + +### google-maps + +| | | +| ---------- | --------------------- | +| bug fix | incorrect event name ([#18027](https://github.com/angular/components/issues/18027)) ([f2cbc06](https://github.com/angular/components/commit/f2cbc06)), closes [#17845](https://github.com/angular/components/issues/17845) [#18026](https://github.com/angular/components/issues/18026) | + +### material-experimental + +| | | +| ---------- | --------------------- | +| bug fix | **mdc-chips:** avoid potential server-side rendering errors ([#18044](https://github.com/angular/components/issues/18044)) ([17a7bcb](https://github.com/angular/components/commit/17a7bcb)) | +| bug fix | **mdc-chips:** checkmark not visible in high contrast black-on-white mode ([#18061](https://github.com/angular/components/issues/18061)) ([99af8e9](https://github.com/angular/components/commit/99af8e9)) | +| bug fix | **mdc-chips:** error on IE and Edge due to unsupported classList ([#18074](https://github.com/angular/components/issues/18074)) ([5fccd24](https://github.com/angular/components/commit/5fccd24)) | +| bug fix | **mdc-chips:** prevent default space and enter ([#18084](https://github.com/angular/components/issues/18084)) ([7b7e633](https://github.com/angular/components/commit/7b7e633)) | +| bug fix | **mdc-chips:** remove aria-hidden from focusable element ([#18054](https://github.com/angular/components/issues/18054)) ([55ee988](https://github.com/angular/components/commit/55ee988)) | +| bug fix | **mdc-chips:** removed chip still focusable ([#18083](https://github.com/angular/components/issues/18083)) ([403377d](https://github.com/angular/components/commit/403377d)) | +| bug fix | **mdc-chips:** Set aria-required on input instead of grid ([#18049](https://github.com/angular/components/issues/18049)) ([22d7f77](https://github.com/angular/components/commit/22d7f77)) | +| bug fix | **mdc-slide-toggle:** fix extra border in latest canary version ([#18035](https://github.com/angular/components/issues/18035)) ([b989701](https://github.com/angular/components/commit/b989701)) | +| bug fix | **mdc-slide-toggle:** initial checked and disabled state not reflected visually ([#17991](https://github.com/angular/components/issues/17991)) ([e746895](https://github.com/angular/components/commit/e746895)) | +| bug fix | **mdc-slide-toggle:** update adapter to match new interface ([#18124](https://github.com/angular/components/issues/18124)) ([636ae5c](https://github.com/angular/components/commit/636ae5c)) | +| bug fix | **mdc-tabs:** incorrect tab text color in dark theme ([#18068](https://github.com/angular/components/issues/18068)) ([86a4ba7](https://github.com/angular/components/commit/86a4ba7)) | +| feature | **mdc-form-field** initial prototype of mdc-form-field ([#17903](https://github.com/angular/components/issues/17903)) ([697c3a0](https://github.com/angular/components/commit/697c3a0)) | + + # 9.0.0-rc.6 "fluoride-floor" (2019-12-19) ### material diff --git a/FAQ.md b/FAQ.md index e7c2027f5006..28e03a660851 100644 --- a/FAQ.md +++ b/FAQ.md @@ -15,7 +15,7 @@ descriptions. ## Are there any updates on issue _X_? Any issue updates will appear on the issue. Due to the large volume of issues and feature requests -recieved by the team, we aren't able to regularly comment on all open issues and PRs. +received by the team, we aren't able to regularly comment on all open issues and PRs. ## Why hasn't PR _X_ been merged? @@ -27,7 +27,7 @@ When tests fail, however, the team has to spend time investigating before the PR Because of this, we cannot merge any PRs that would break an existing project. If a PR has extensive failures, it may be put on the backburner until the team can schedule time to debug the full extent of the issue. If a PR seems ready to merge, but has been inactive, it has very likely -encountered some test failures inside Google that must be resovled first. +encountered some test failures inside Google that must be resolved first. ## Why aren't you working on _X_? diff --git a/LICENSE b/LICENSE index 8cc76f6c185d..055a3af98bd8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2019 Google LLC. +Copyright (c) 2020 Google LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 51ae0e165680..ff01e9bee30b 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ The Angular team builds and maintains both common UI components and tools to help you build your own custom components. The team maintains several npm packages. -| Package | Description | Docs | -|---------------------------|------------------------------------------------------------------------------------|------------------| -| `@angular/cdk` | Library that helps you author custom UI components with common interation patterns | [Docs][cdk-docs] | -| `@angular/material` | [Material Design][] UI components for Angular applications | [Docs][mat-docs] | -| `@angular/google-maps` | Angular components built on top of the [Google Maps JavaScript API][] | [Docs][map-docs] | -| `@angular/youtube-player` | Angular component built on top of the [YouTube Player API][] | [Docs][ytp-docs] | +| Package | Description | Docs | +| ------------------------- | ----------------------------------------------------------------------------------- | ---------------- | +| `@angular/cdk` | Library that helps you author custom UI components with common interaction patterns | [Docs][cdk-docs] | +| `@angular/material` | [Material Design][] UI components for Angular applications | [Docs][mat-docs] | +| `@angular/google-maps` | Angular components built on top of the [Google Maps JavaScript API][] | [Docs][map-docs] | +| `@angular/youtube-player` | Angular component built on top of the [YouTube Player API][] | [Docs][ytp-docs] | #### Quick links @@ -33,13 +33,16 @@ If you'd like to contribute, please follow our [contributing guidelines][contrib our [`help wanted`][help-wanted] label for a list of issues with good opportunities for contribution. -## High level work planned for Q4 2019 (Oct - Dec): -* Remove dependency on HammerJS -* Finish remaining test harnesses for Angular Material components +## High level work planned for Q1 2020 (Jan - Mar): +* Date-range picker +* Finish remaining test harnesses for Angular Material components (four remaining as of January) * Continuing to create new, API-compatible versions of the Angular Material components backed by -[MDC Web][] ([see @jelbourn's ng-conf talk](https://youtu.be/4EXQKP-Sihw?t=891)). -* New `@angular/google-maps` package -* New `@angular/cdk/clipboard` subpackage +[MDC Web][] ([see @jelbourn's ng-conf talk](https://youtu.be/4EXQKP-Sihw?t=891)). There are five +remaining components to complete here as of January. +* Add support for density configuration for the new components based on MDC Web. +* Authoring benchmarks to collect performance metrics for Angular Material components. These +benchmarks will live inside Google's internal code repository for the time being, but we should +be able to publish the results. ## About the team diff --git a/WORKSPACE b/WORKSPACE index 53b89bd32c40..af4ff7fff2d9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -8,21 +8,25 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Add NodeJS rules http_archive( name = "build_bazel_rules_nodejs", - sha256 = "a54b2511d6dae42c1f7cdaeb08144ee2808193a088004fc3b464a04583d5aa2e", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.42.3/rules_nodejs-0.42.3.tar.gz"], + sha256 = "3887b948779431ac443e6a64f31b9e1e17b8d386a31eebc50ec1d9b0a6cabd2b", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.0.0/rules_nodejs-1.0.0.tar.gz"], ) # Add sass rules http_archive( name = "io_bazel_rules_sass", - sha256 = "ad08e8c82aa1f48b72dc295cb83bba33f514cdf24cc7b0e21e9353f20f0dc147", - strip_prefix = "rules_sass-5d1b26f8cd12c5d75dd359f9291090b341e8fd52", - # We need to use a version that includes SHA 5d1b26f8cd12c5d75dd359f9291090b341e8fd52 of - # the "rules_sass" repository as it adds support for workers. - url = "https://github.com/bazelbuild/rules_sass/archive/5d1b26f8cd12c5d75dd359f9291090b341e8fd52.zip", + # Patch `rules_sass` to work around a bug that causes error messages to be not + # printed in worker mode: https://github.com/bazelbuild/rules_sass/issues/96. + # TODO(devversion): remove this patch once the Sass Node entry-point returns a `Promise`. + patches = ["//tools/bazel:sass_worker_async.patch"], + sha256 = "c78be58f5e0a29a04686b628cf54faaee0094322ae0ac99da5a8a8afca59a647", + strip_prefix = "rules_sass-1.25.0", + urls = [ + "https://github.com/bazelbuild/rules_sass/archive/1.25.0.zip", + ], ) -load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") +load("@build_bazel_rules_nodejs//:index.bzl", "check_bazel_version", "node_repositories", "yarn_install") # The minimum bazel version to use with this repo is v1.1.0. check_bazel_version("1.1.0") diff --git a/guides/creating-a-custom-form-field-control.md b/guides/creating-a-custom-form-field-control.md index a6d3d8f98bd0..64d73d5819c9 100644 --- a/guides/creating-a-custom-form-field-control.md +++ b/guides/creating-a-custom-form-field-control.md @@ -24,11 +24,11 @@ class MyTel { selector: 'example-tel-input', template: `
- + - + - +
`, styles: [` diff --git a/guides/v9-hammerjs-migration.md b/guides/v9-hammerjs-migration.md index 9d3b0c6f788e..ccac8c438903 100644 --- a/guides/v9-hammerjs-migration.md +++ b/guides/v9-hammerjs-migration.md @@ -11,7 +11,7 @@ deprecated and will be removed in version 10. Since HammerJS previously was a requirement for a few Angular Material components, projects might have installed `HammerJS` exclusively for Angular Material. Since HammerJS is no longer needed when -updating to v9, the dependency on HammerJS can be removed if it's not used directly in the +updating to v9, the dependency on HammerJS can be removed if it's not used directly in your application. In some cases, projects use HammerJS events in templates while relying on Angular Material @@ -21,21 +21,21 @@ these HammerJS events. ### What does the migration do? -The migration automatically removes HammerJS from the project if HammerJS is not used. +The migration automatically removes HammerJS from your project if HammerJS is not used. Additionally, Angular Material's `GestureConfig` (now deprecated) defined custom HammerJS gestures. -If the application directly uses any of these gestures, the migration will introduce a new +If your application directly uses any of these gestures, the migration will introduce a new application-specific configuration for these custom gestures, removing the dependency on Angular Material's `GestureConfig`. -Finally, if the application uses any of the custom HammerJS gestures provided by Angular Material's +Finally, if your application uses any of the custom HammerJS gestures provided by Angular Material's `GestureConfig`, or the default HammerJS gestures, the migration will add an import for Angular's new `HammerModule`, which enabled HammerJS event bindings. These bindings were previously enabled by default in Angular versions 8 and below. If your application provides a custom [`HAMMER_GESTURE_CONFIG`][1] and also references the deprecated Angular Material `GestureConfig`, the migration will print a warning about -ambiguous usage. The migration cannot migrate the project automatically and manual changes +ambiguous usage. The migration cannot migrate your project automatically and manual changes are required. Read more [in the dedicated section](#The-migration-reported-ambiguous-usage-What-should-I-do). ### How does the schematic remove HammerJS? @@ -43,7 +43,7 @@ are required. Read more [in the dedicated section](#The-migration-reported-ambig HammerJS can be set up in many ways. The migration handles the most common cases, covering approaches recommended by Angular Material in the past. The migration performs the following steps: -*1\.* Remove `hammerjs` from the project `package.json`. +*1\.* Remove `hammerjs` from your project `package.json`. ```json { "dependencies": { @@ -62,7 +62,7 @@ import 'hammerjs'; The migration cannot automatically remove HammerJS from tests. Please manually clean up the test setup and resolve any test issues. Read more in a -[dedicated section for test migration](#How-to-migrate-my-tests) +[dedicated section for test migration](#How-to-migrate-my-tests). ### How do I migrate references to the deprecated `GestureConfig`? @@ -81,7 +81,7 @@ import {GestureConfig} from '@angular/material/core'; export class AppModule {} ``` -If this pattern is found in the project, it usually means that a component relies on the +If this pattern is found in your project, it usually means that a component relies on the deprecated `GestureConfig` in order to use HammerJS events in the template. If this is the case, the migration automatically creates a new gesture config which supports the used HammerJS events. All references to the deprecated gesture config will be rewritten to the newly created one. @@ -91,25 +91,25 @@ from the module. This is automatically done by the migration. There are other patterns where the deprecated `GestureConfig` is extended, injected or used in combination with a different custom gesture config. These patterns cannot be handled -automatically, but the migration will report such patterns and ask for manual cleanup. +automatically, but the migration will report such patterns and ask you to perform manual cleanup. ### How to migrate my tests? Components in your project might use Angular Material components which previously depended -on HammerJS. There might be unit tests for these components which also test gesture functionality -of the Angular Material components. For such unit tests, find all failing gesture tests. These -might need to be reworked to dispatch proper events to simulate gestures, or need to be deleted. -Specifically gesture tests for the `` should be removed. This is because the -`` no longer supports gestures. +upon HammerJS. There might be unit tests for these components which also test gesture functionality +of the Angular Material components. For such unit tests, identify all failing gesture tests. Then +you should rework these tests to dispatch proper events, in order to simulate gestures, or +delete the tests. Specifically gesture tests for the `` should be removed. +This is because the `` no longer supports gestures. If some unit tests depend on the deprecated Angular Material `GestureConfig` to simulate gesture -events, the reference should be either removed and tests reworked to use DOM events, or the +events, the reference should either be removed and tests reworked to use DOM events, or the reference should be changed to the new gesture config created by the migration. -If HammerJS has been removed by the migration from the project, you might able to need to -clean up test setup that provides HammerJS. This is usually done in the test main file (usually -in `src/test.ts`) where `hammerjs` is imported. +If HammerJS has been removed from your project by the migration, you might need to refactor +the test setup that provides HammerJS. This is usually done in your test main file (usually +in `src/test.ts`) where `hammerjs` may be imported. ```typescript import 'hammerjs'; @@ -128,15 +128,17 @@ plugin, or to an actual `@Output`. For example: ``` In the example above, `rotate` could be an event from the deprecated `GestureConfig`, or an -`@Output` from ``. The migration warns about this to raise awareness that it -might have _incorrectly kept_ HammerJS. Please manually check if you can remove HammerJS. +`@Output` from ``. The migration warns you about this to raise awareness that it +might have _incorrectly kept_ HammerJS. Please check if you can remove HammerJS from the project +manually. **Case 2**: The deprecated Angular Material `GestureConfig` is used in combination with a custom [`HAMMER_GESTURE_CONFIG`][1]. This case is ambiguous because the migration is unable to detect whether a given HammerJS event binding corresponds to the custom gesture config, or to -the deprecated Angular Material gesture config. If such a warning has been reported to you, check -if you can remove references to the deprecated `GestureConfig`, or if you need to handle the events -provided by the deprecated gesture config in your existing custom gesture config. +the deprecated Angular Material gesture config. If such a warning has been reported, check +if you can remove the references to the deprecated `GestureConfig`, or if you need to update your +existing, custom gesture config to handle the events provided by the deprecated Angular Material +`GestureConfig`. [1]: https://v9.angular.io/api/platform-browser/HammerGestureConfig [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_a_module_for_its_side_effects_only diff --git a/package.json b/package.json index f58f181189c0..2059f6057524 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,9 @@ "bazel:buildifier": "find . -type f \\( -name \"*.bzl\" -or -name WORKSPACE -or -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs buildifier -v --warnings=attr-cfg,attr-license,attr-non-empty,attr-output-default,attr-single-file,constant-glob,ctx-args,depset-iteration,depset-union,dict-concatenation,duplicated-name,filetype,git-repository,http-archive,integer-division,load,load-on-top,native-build,native-package,output-group,package-name,package-on-top,redefined-variable,repository-name,same-origin-load,string-iteration,unused-variable,unsorted-dict-items,out-of-order-load", "bazel:format-lint": "yarn -s bazel:buildifier --lint=warn --mode=check", "dev-app": "ibazel run //src/dev-app:devserver", - "test": "bazel test //src/... --test_tag_filters=-e2e,-browser:firefox-local --build_tag_filters=-browser:firefox-local --build_tests_only", - "test-firefox": "bazel test //src/... --test_tag_filters=-e2e,-browser:chromium-local --build_tag_filters=-browser:chromium-local --build_tests_only", + "test": "node ./scripts/run-component-tests.js", + "test-local": "yarn -s test --local", + "test-firefox": "yarn -s test --firefox", "lint": "yarn -s tslint && yarn -s bazel:format-lint && yarn -s ownerslint", "e2e": "bazel test //src/... --test_tag_filters=e2e", "deploy-dev-app": "node ./scripts/deploy-dev-app.js", @@ -28,55 +29,56 @@ "gulp": "gulp", "stage-release": "ts-node --project tools/release/ tools/release/stage-release.ts", "publish-release": "ts-node --project tools/release/ tools/release/publish-release.ts", + "check-entry-point-setup": "node ./scripts/check-entry-point-setup.js", "check-release-output": "ts-node --project tools/release tools/release/check-release-output.ts", + "check-rollup-globals": "ts-node --project scripts/ scripts/check-rollup-globals.ts", "changelog": "ts-node --project tools/release tools/release/changelog.ts", "preinstall": "node ./tools/npm/check-npm.js", "format:ts": "git-clang-format HEAD $(git diff HEAD --name-only | grep -v \"\\.d\\.ts\")", "format:bazel": "yarn -s bazel:buildifier --lint=fix --mode=fix", "format": "yarn -s format:ts && yarn -s format:bazel", "cherry-pick-patch": "ts-node --project tools/cherry-pick-patch/ tools/cherry-pick-patch/cherry-pick-patch.ts", - "check-rollup-globals": "ts-node --project scripts/ scripts/check-rollup-globals.ts", "ownerslint": "ts-node --project scripts/ scripts/ownerslint.ts", "tslint": "tslint -c tslint.json --project ./tsconfig.json", "stylelint": "stylelint \"src/**/*.+(css|scss)\" --config .stylelintrc.json --syntax scss" }, - "version": "9.0.0-rc.6", + "version": "9.0.0-rc.9", "dependencies": { - "@angular/animations": "^9.0.0-rc.7", - "@angular/common": "^9.0.0-rc.7", - "@angular/compiler": "^9.0.0-rc.7", - "@angular/core": "^9.0.0-rc.7", - "@angular/elements": "^9.0.0-rc.7", - "@angular/forms": "^9.0.0-rc.7", - "@angular/platform-browser": "^9.0.0-rc.7", + "@angular/animations": "^9.0.0-rc.12", + "@angular/common": "^9.0.0-rc.12", + "@angular/compiler": "^9.0.0-rc.12", + "@angular/core": "^9.0.0-rc.12", + "@angular/elements": "^9.0.0-rc.12", + "@angular/forms": "^9.0.0-rc.12", + "@angular/platform-browser": "^9.0.0-rc.12", "@types/googlemaps": "^3.37.0", "@types/youtube": "^0.0.38", "@webcomponents/custom-elements": "^1.1.0", "core-js": "^2.6.9", - "material-components-web": "5.0.0-canary.50f110a6c.0", + "material-components-web": "5.0.0-canary.80a4d326f.0", "rxjs": "^6.5.3", "systemjs": "0.19.43", "tslib": "^1.10.0", "zone.js": "~0.10.2" }, "devDependencies": { - "@angular-devkit/core": "^9.0.0-rc.6", - "@angular-devkit/schematics": "^9.0.0-rc.6", - "@angular/bazel": "^9.0.0-rc.7", - "@angular/compiler-cli": "^9.0.0-rc.7", - "@angular/platform-browser-dynamic": "^9.0.0-rc.7", - "@angular/platform-server": "^9.0.0-rc.7", - "@angular/router": "^9.0.0-rc.7", + "@angular-devkit/core": "^9.0.0-rc.11", + "@angular-devkit/schematics": "^9.0.0-rc.11", + "@angular/bazel": "^9.0.0-rc.12", + "@angular/compiler-cli": "^9.0.0-rc.12", + "@angular/platform-browser-dynamic": "^9.0.0-rc.12", + "@angular/platform-server": "^9.0.0-rc.12", + "@angular/router": "^9.0.0-rc.12", "@bazel/bazel": "^1.1.0", "@bazel/buildifier": "^0.29.0", "@bazel/ibazel": "^0.10.3", - "@bazel/jasmine": "^0.41.0", - "@bazel/karma": "^0.41.0", - "@bazel/protractor": "^0.41.0", - "@bazel/typescript": "^0.41.0", + "@bazel/jasmine": "1.0.0", + "@bazel/karma": "1.0.0", + "@bazel/protractor": "1.0.0", + "@bazel/typescript": "1.0.0", "@firebase/app-types": "^0.3.2", "@octokit/rest": "^16.28.7", - "@schematics/angular": "^9.0.0-rc.6", + "@schematics/angular": "^9.0.0-rc.11", "@types/browser-sync": "^2.26.1", "@types/fs-extra": "^4.0.3", "@types/glob": "^5.0.33", @@ -126,7 +128,7 @@ "minimatch": "^3.0.4", "minimist": "^1.2.0", "moment": "^2.18.1", - "node-sass": "^4.12.0", + "sass": "^1.24.4", "parse5": "^5.0.0", "protractor": "^5.4.2", "requirejs": "^2.3.6", @@ -141,17 +143,18 @@ "semver": "^6.3.0", "send": "^0.17.1", "shelljs": "^0.8.3", - "stylelint": "^10.1.0", + "stylelint": "^12.0.0", "terser": "^4.3.9", - "ts-api-guardian": "^0.4.6", + "ts-api-guardian": "^0.5.0", "ts-node": "^3.0.4", + "tsickle": "0.38.0", "tslint": "^5.20.0", "tsutils": "^3.0.0", - "typescript": "^3.6.4", + "typescript": "~3.7.4", "vrsource-tslint-rules": "5.1.1" }, "resolutions": { - "dgeni-packages/typescript": "3.6.4", + "dgeni-packages/typescript": "3.7.4", "**/graceful-fs": "4.2.2" } } diff --git a/packages.bzl b/packages.bzl index 4757d2b56627..a85e0a0fe297 100644 --- a/packages.bzl +++ b/packages.bzl @@ -13,7 +13,7 @@ VERSION_PLACEHOLDER_REPLACEMENTS = { # List of default Angular library UMD bundles which are not processed by ngcc. ANGULAR_NO_NGCC_BUNDLES = [ - "@npm//:node_modules/@angular/compiler/bundles/compiler.umd.js", + ("@angular/compiler", ["compiler.umd.js"]), ] # List of Angular library UMD bundles which will are processed by ngcc. @@ -29,20 +29,39 @@ ANGULAR_NGCC_BUNDLES = [ ("@angular/router", ["router.umd.js"]), ] -ANGULAR_LIBRARY_VIEW_ENGINE_UMDS = ANGULAR_NO_NGCC_BUNDLES + [ - "@npm//:node_modules/%s/bundles/%s" % (pkgName, bundleName) - for pkgName, bundleNames in ANGULAR_NGCC_BUNDLES - for bundleName in bundleNames -] -ANGULAR_LIBRARY_IVY_UMDS = ANGULAR_NO_NGCC_BUNDLES + [ - "@npm//:node_modules/%s/__ivy_ngcc__/bundles/%s" % (pkgName, bundleName) - for pkgName, bundleNames in ANGULAR_NGCC_BUNDLES - for bundleName in bundleNames -] +""" + Gets a dictionary of all packages and their bundle names. +""" + +def getFrameworkPackageBundles(): + res = {} + for pkgName, bundleNames in ANGULAR_NGCC_BUNDLES + ANGULAR_NO_NGCC_BUNDLES: + res[pkgName] = res.get(pkgName, []) + bundleNames + return res + +""" + Gets a list of labels which resolve to the UMD bundles of the given packages. +""" + +def getUmdFilePaths(packages, ngcc_artifacts): + tmpl = "@npm//:node_modules/%s" + ("/__ivy_ngcc__" if ngcc_artifacts else "") + "/bundles/%s" + return [ + tmpl % (pkgName, bundleName) + for pkgName, bundleNames in packages + for bundleName in bundleNames + ] + +ANGULAR_PACKAGE_BUNDLES = getFrameworkPackageBundles() + +ANGULAR_LIBRARY_VIEW_ENGINE_UMDS = getUmdFilePaths(ANGULAR_NO_NGCC_BUNDLES, False) + \ + getUmdFilePaths(ANGULAR_NGCC_BUNDLES, False) + +ANGULAR_LIBRARY_IVY_UMDS = getUmdFilePaths(ANGULAR_NO_NGCC_BUNDLES, False) + \ + getUmdFilePaths(ANGULAR_NGCC_BUNDLES, True) """ Gets the list of targets for the Angular library UMD bundles. Conditionally - switches between View Engine or Ivy UMD bundles based on the + switches between View Engine or Ivy UMD bundles based on the "--config={ivy,view-engine}" flag. """ diff --git a/rollup-globals.bzl b/rollup-globals.bzl index ce23975ec919..2f0ae3966c3b 100644 --- a/rollup-globals.bzl +++ b/rollup-globals.bzl @@ -65,7 +65,7 @@ ROLLUP_GLOBALS = { "@material/tab-bar": "mdc.tabBar", "@material/tab-indicator": "mdc.tabIndicator", "@material/tab-scroller": "mdc.tabScroller", - "@material/text-field": "mdc.textField", + "@material/textfield": "mdc.textfield", "@material/top-app-bar": "mdc.topAppBar", # Third-party libraries. diff --git a/scripts/check-entry-point-setup.js b/scripts/check-entry-point-setup.js new file mode 100644 index 000000000000..db1db00fa4af --- /dev/null +++ b/scripts/check-entry-point-setup.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +/** + * Script that detects and validates entry-points. The script walks through all + * source files in the code base and ensures that determined entry-points are + * configured. The list of configured entry-points in Starlark is passed to the + * script through a manifest file (generated by Bazel) + */ + +const {join, dirname} = require('path'); +const {sync: globSync} = require('glob'); +const minimatch = require('minimatch'); +const fs = require('fs'); +const chalk = require('chalk'); + +const [entryPointManifest] = process.argv.slice(2); +const entryPoints = JSON.parse(fs.readFileSync(entryPointManifest, 'utf8')); +const packagesDir = join(__dirname, '../src'); + +/** + * Globs that matches directories which should never be considered + * as entry-points. + */ +const excludeGlobs = [ + 'cdk/testing/private', + '*/schematics/**', +]; + +/** List of detected entry-points which are not properly configured. */ +const nonConfigured = []; + +// We require a minimum depth of two. This ensures that we only check entry-points and +// do not check package names (like "cdk"). There is no validation for package names yet. +globSync('*/*/**/public-api.ts', {cwd: packagesDir}).forEach(filePath => { + const entryPointName = dirname(filePath); + + if (!excludeGlobs.some(pattern => minimatch(entryPointName, pattern)) && + !entryPoints.includes(entryPointName)) { + nonConfigured.push(entryPointName); + } +}); + +if (nonConfigured.length) { + console.error(chalk.red('Found entry-points which are not configured. Add the following ' + + 'entry-points to the package-specific "config.bzl" file:\n')); + nonConfigured.forEach(e => console.warn(chalk.yellow(` - ${e}`))); + process.exit(1); +} else { + console.log(chalk.green('All detected entry-points are configured properly.')); +} diff --git a/scripts/run-component-tests.js b/scripts/run-component-tests.js new file mode 100644 index 000000000000..85cf9ff0536b --- /dev/null +++ b/scripts/run-component-tests.js @@ -0,0 +1,107 @@ +#!/usr/bin/env node + +/** + * Script that simplifies the workflow of running unit tests for a component + * using Bazel. Here are a few examples: + * + * node ./scripts/run-component-tests button | Runs Material button tests + * node ./scripts/run-component-tests overlay | Runs CDK overlay tests + * node ./scripts/run-component-tests src/cdk/a11y | Runs CDK a11y tests + * node ./scripts/run-component-tests a11y overlay | Runs CDK a11y and overlay tests + * + * Supported command line flags: + * + * --local | If specified, no browser will be launched. + * --firefox | Instead of Chrome being used for tests, Firefox will be used. + * --no-watch | Watch mode is enabled by default. This flag opts-out to standard Bazel. + */ + +const minimist = require('minimist'); +const shelljs = require('shelljs'); +const chalk = require('chalk'); +const path = require('path'); +const args = process.argv.slice(2); + +// Path to the project directory. +const projectDir = path.join(__dirname, '../'); + +// Path to the directory that contains all packages. +const packagesDir = path.join(projectDir, 'src/'); + +// List of packages where the specified component could be defined in. The script uses the +// first package that contains the component (if no package is specified explicitly). +// e.g. "button" will become "material/button", and "overlay" becomes "cdk/overlay". +const orderedGuessPackages = ['material', 'cdk', 'material-experimental', 'cdk-experimental']; + +// ShellJS should exit if any command fails. +shelljs.set('-e'); +shelljs.cd(projectDir); + +// Extracts the supported command line options. +const {_: components, local, firefox, watch} = minimist(args, { + boolean: ['local', 'firefox', 'watch'], + default: {watch: true}, +}); + +// Exit if no component has been specified. +if (!components.length) { + console.error(chalk.red( + 'No component specified. Specify a component name, or pass a ' + + 'path to the component directory.')); + process.exit(1); +} + +// We can only run a single target with "--local". Running multiple targets within the +// same Karma server is not possible since each test target runs isolated from the others. +if (local && components.length > 1) { + console.error(chalk.red( + 'Unable to run multiple components tests in local mode. ' + + 'Only one component at a time can be run with "--local"')); + process.exit(1); +} + +const bazelBinary = watch ? 'ibazel' : 'bazel'; +const bazelAction = local ? 'run' : 'test'; +const testTargetName = + `unit_tests_${local ? 'local' : firefox ? 'firefox-local' : 'chromium-local'}`; +const testLabels = components.map(t => `${getBazelPackageOfComponentName(t)}:${testTargetName}`); + +// Runs Bazel for the determined test labels. +shelljs.exec(`yarn -s ${bazelBinary} ${bazelAction} ${testLabels.join(' ')}`); + +/** + * Gets the Bazel package label for the specified component name. Throws if + * the component could not be resolved to a Bazel package. + */ +function getBazelPackageOfComponentName(name) { + // Before guessing any Bazel package, we test if the name contains the + // package name already. If so, we just use that for Bazel package. + const targetName = convertPathToBazelLabel(name); + if (targetName !== null) { + return targetName; + } + // If the name does not contain an explicit package name, we try guessing the + // package name by walking through an ordered list of possible packages and checking + // if a package contains a component with the given name. The first match will be used. + for (let guessPackage of orderedGuessPackages) { + const guessTargetName = convertPathToBazelLabel(path.join(packagesDir, guessPackage, name)); + if (guessTargetName !== null) { + return guessTargetName; + } + } + throw Error(chalk.red(`Could not find test target for specified component: ` + + `${chalk.yellow(name)}. Looked in packages: ${orderedGuessPackages.join(', ')}`)); +} + +/** Converts a path to a Bazel label. */ +function convertPathToBazelLabel(name) { + if (shelljs.test('-d', name)) { + return `//${convertPathToPosix(path.relative(projectDir, name))}`; + } + return null; +} + +/** Converts an arbitrary path to a Posix path. */ +function convertPathToPosix(pathName) { + return pathName.replace(/\\/g, '/'); +} diff --git a/src/a11y-demo/dialog/dialog-neptune-a11y.html b/src/a11y-demo/dialog/dialog-neptune-a11y.html index fd9e67a2e941..6fae92080cb3 100644 --- a/src/a11y-demo/dialog/dialog-neptune-a11y.html +++ b/src/a11y-demo/dialog/dialog-neptune-a11y.html @@ -22,6 +22,6 @@

Neptune

Read more on Wikipedia - diff --git a/src/a11y-demo/progress-spinner/progress-spinner-a11y.html b/src/a11y-demo/progress-spinner/progress-spinner-a11y.html index acf69bb60136..b4ee49ed3901 100644 --- a/src/a11y-demo/progress-spinner/progress-spinner-a11y.html +++ b/src/a11y-demo/progress-spinner/progress-spinner-a11y.html @@ -1,6 +1,6 @@

Loading indicator (Indeterminate progress spinner)

- +
diff --git a/src/bazel-tsconfig-build.json b/src/bazel-tsconfig-build.json index 4a69601d0714..ac3edaff1dab 100644 --- a/src/bazel-tsconfig-build.json +++ b/src/bazel-tsconfig-build.json @@ -18,6 +18,7 @@ "noImplicitAny": true, "noImplicitThis": true, "importHelpers": true, + "strictBindCallApply": true, "newLine": "lf", "module": "es2015", "moduleResolution": "node", diff --git a/src/cdk-experimental/dialog/BUILD.bazel b/src/cdk-experimental/dialog/BUILD.bazel index e2de700d6113..0cdb1c03e45f 100644 --- a/src/cdk-experimental/dialog/BUILD.bazel +++ b/src/cdk-experimental/dialog/BUILD.bazel @@ -17,7 +17,6 @@ ng_module( "//src/cdk/overlay", "//src/cdk/portal", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/cdk-experimental/dialog/dialog-module.ts b/src/cdk-experimental/dialog/dialog-module.ts index 234f6a325332..e8472bd8bf5c 100644 --- a/src/cdk-experimental/dialog/dialog-module.ts +++ b/src/cdk-experimental/dialog/dialog-module.ts @@ -7,7 +7,6 @@ */ import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; import {OverlayModule} from '@angular/cdk/overlay'; import {PortalModule} from '@angular/cdk/portal'; import {A11yModule} from '@angular/cdk/a11y'; @@ -25,7 +24,6 @@ import { @NgModule({ imports: [ - CommonModule, OverlayModule, PortalModule, A11yModule, diff --git a/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts b/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts index 70c96ba20a5e..ff373b3a0094 100644 --- a/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts +++ b/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts @@ -47,7 +47,7 @@ export const enum HoverContentState { */ @Injectable() export class EditEventDispatcher { - /** A subject that indicates which table cell is currently editing. */ + /** A subject that indicates which table cell is currently editing (unless it is disabled). */ readonly editing = new Subject(); /** A subject that indicates which table row is currently hovered. */ @@ -62,6 +62,13 @@ export class EditEventDispatcher { /** A subject that emits mouse move events from the table indicating the targeted row. */ readonly mouseMove = new Subject(); + // TODO: Use WeakSet once IE11 support is dropped. + /** + * Tracks the currently disabled editable cells - edit calls will be ignored + * for these cells. + */ + readonly disabledCells = new WeakMap(); + /** The EditRef for the currently active edit lens (if any). */ get editRef(): EditRef|null { return this._editRef; @@ -81,9 +88,14 @@ export class EditEventDispatcher { this._distinctUntilChanged as MonoTypeOperatorFunction, ); + readonly editingAndEnabled = this.editing.pipe( + filter(cell => cell == null || !this.disabledCells.has(cell)), + share(), + ); + /** An observable that emits the row containing focus or an active edit. */ readonly editingOrFocused = combineLatest([ - this.editing.pipe( + this.editingAndEnabled.pipe( map(cell => closest(cell, ROW_SELECTOR)), this._startWithNull, ), @@ -126,7 +138,7 @@ export class EditEventDispatcher { share(), ); - private readonly _editingDistinct = this.editing.pipe( + private readonly _editingAndEnabledDistinct = this.editingAndEnabled.pipe( distinctUntilChanged(), this._enterZone(), share(), @@ -138,7 +150,7 @@ export class EditEventDispatcher { private _lastSeenRowHoverOrFocus: Observable|null = null; constructor(private readonly _ngZone: NgZone) { - this._editingDistinct.subscribe(cell => { + this._editingAndEnabledDistinct.subscribe(cell => { this._currentlyEditing = cell; }); } @@ -150,7 +162,7 @@ export class EditEventDispatcher { editingCell(element: Element|EventTarget): Observable { let cell: Element|null = null; - return this._editingDistinct.pipe( + return this._editingAndEnabledDistinct.pipe( map(editCell => editCell === (cell || (cell = closest(element, CELL_SELECTOR)))), this._distinctUntilChanged as MonoTypeOperatorFunction, ); diff --git a/src/cdk-experimental/popover-edit/lens-directives.ts b/src/cdk-experimental/popover-edit/lens-directives.ts index c591bb5a6177..7f1865af4302 100644 --- a/src/cdk-experimental/popover-edit/lens-directives.ts +++ b/src/cdk-experimental/popover-edit/lens-directives.ts @@ -175,24 +175,27 @@ export class CdkEditRevert { } /** Closes the lens on click. */ -@Directive({ - selector: 'button[cdkEditClose]', - host: { - 'type': 'button', // Prevents accidental form submits. - } -}) +@Directive({selector: '[cdkEditClose]'}) export class CdkEditClose { - /** Type of the button. Defaults to `button` to avoid accident form submits. */ - @Input() type: string = 'button'; - constructor( - protected readonly editRef: EditRef) {} + protected readonly elementRef: ElementRef, + protected readonly editRef: EditRef) { + + const nativeElement = elementRef.nativeElement; + + // Prevent accidental form submits. + if (nativeElement.nodeName === 'BUTTON' && !nativeElement.getAttribute('type')) { + nativeElement.setAttribute('type', 'button'); + } + } // In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order // to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we // can move this back into `host`. // tslint:disable:no-host-decorator-in-concrete @HostListener('click') + @HostListener('keyup.enter') + @HostListener('keyup.space') closeEdit(): void { // Note that we use `click` here, rather than a keyboard event, because some screen readers // will emit a fake click event instead of an enter keyboard event on buttons. diff --git a/src/cdk-experimental/popover-edit/popover-edit.spec.ts b/src/cdk-experimental/popover-edit/popover-edit.spec.ts index 955ccf11719c..fe7edee66585 100644 --- a/src/cdk-experimental/popover-edit/popover-edit.spec.ts +++ b/src/cdk-experimental/popover-edit/popover-edit.spec.ts @@ -52,7 +52,11 @@ const CELL_TEMPLATE = ` `; -const POPOVER_EDIT_DIRECTIVE_NAME = `[cdkPopoverEdit]="nameEdit" [cdkPopoverEditColspan]="colspan"`; +const POPOVER_EDIT_DIRECTIVE_NAME = ` + [cdkPopoverEdit]="nameEdit" + [cdkPopoverEditColspan]="colspan" + [cdkPopoverEditDisabled]="nameEditDisabled" + `; const POPOVER_EDIT_DIRECTIVE_WEIGHT = `[cdkPopoverEdit]="weightEdit" cdkPopoverEditTabOut`; @@ -67,6 +71,7 @@ abstract class BaseTestComponent { preservedValues = new FormValueContainer(); + nameEditDisabled = false; ignoreSubmitUnlessValid = true; clickOutBehavior: PopoverEditClickOutBehavior = 'close'; colspan: CdkPopoverEditColspan = {}; @@ -376,9 +381,7 @@ describe('CDK Popover Edit', () => { expect(component.hoverContentStateForRow(rows.length - 1)) .toBe(HoverContentState.FOCUSABLE); })); - }); - describe('triggering edit', () => { it('shows and hides on-hover content only after a delay', fakeAsync(() => { const [row0, row1] = component.getRows(); row0.dispatchEvent(new Event('mouseover', {bubbles: true})); @@ -478,11 +481,35 @@ describe('CDK Popover Edit', () => { expect(component.lensIsOpen()).toBe(true); clearLeftoverTimers(); })); + + it('does not trigger edit when disabled', fakeAsync(() => { + component.nameEditDisabled = true; + fixture.detectChanges(); + + // Uses Enter to open the lens. + component.openLens(); + + expect(component.lensIsOpen()).toBe(false); + clearLeftoverTimers(); + })); }); describe('focus manipulation', () => { const getRowCells = () => component.getRows().map(getCells); + describe('tabindex', () => { + it('sets tabindex to 0 on editable cells', () => { + expect(component.getEditCell().getAttribute('tabindex')).toBe('0'); + }); + + it('unsets tabindex to 0 on disabled cells', () => { + component.nameEditDisabled = true; + fixture.detectChanges(); + + expect(component.getEditCell().hasAttribute('tabindex')).toBe(false); + }); + }); + describe('arrow keys', () => { const dispatchKey = (cell: HTMLElement, keyCode: number) => dispatchKeyboardEvent(cell, 'keydown', keyCode, undefined, cell); diff --git a/src/cdk-experimental/popover-edit/table-directives.ts b/src/cdk-experimental/popover-edit/table-directives.ts index 3fc52ac7343f..52f81fad219c 100644 --- a/src/cdk-experimental/popover-edit/table-directives.ts +++ b/src/cdk-experimental/popover-edit/table-directives.ts @@ -148,15 +148,16 @@ export class CdkEditable implements AfterViewInit, OnDestroy { } const POPOVER_EDIT_HOST_BINDINGS = { - 'tabIndex': '0', + '[attr.tabindex]': 'disabled ? null : 0', 'class': 'cdk-popover-edit-cell', - '[attr.aria-haspopup]': 'true', + '[attr.aria-haspopup]': '!disabled', }; const POPOVER_EDIT_INPUTS = [ 'template: cdkPopoverEdit', 'context: cdkPopoverEditContext', 'colspan: cdkPopoverEditColspan', + 'disabled: cdkPopoverEditDisabled', ]; /** @@ -200,6 +201,22 @@ export class CdkPopoverEdit implements AfterViewInit, OnDestroy { } private _colspan: CdkPopoverEditColspan = {}; + /** Whether popover edit is disabled for this cell. */ + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = value; + + if (value) { + this.services.editEventDispatcher.doneEditingCell(this.elementRef.nativeElement!); + this.services.editEventDispatcher.disabledCells.set(this.elementRef.nativeElement!, true); + } else { + this.services.editEventDispatcher.disabledCells.delete(this.elementRef.nativeElement!); + } + } + private _disabled = false; + protected focusTrap?: FocusTrap; protected overlayRef?: OverlayRef; protected readonly destroyed = new Subject(); @@ -439,8 +456,10 @@ export class CdkRowHoverContent implements AfterViewInit, OnDestroy { if (!this.viewRef) { this.viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef, {}); this.initElement(this.viewRef.rootNodes[0] as HTMLElement); + this.viewRef.markForCheck(); } else if (this.viewContainerRef.indexOf(this.viewRef) === -1) { this.viewContainerRef.insert(this.viewRef!); + this.viewRef.markForCheck(); } if (eventState === HoverContentState.ON) { diff --git a/src/cdk/BUILD.bazel b/src/cdk/BUILD.bazel index 4aa3846d34c1..5a18d4acfcb6 100644 --- a/src/cdk/BUILD.bazel +++ b/src/cdk/BUILD.bazel @@ -51,7 +51,7 @@ ng_package( srcs = ["package.json"], data = rerootedStyleTargets + CDK_SCSS_LIBS, entry_point = ":public-api.ts", - packages = [ + nested_packages = [ "//src/cdk/schematics:npm_package", ], tags = ["release-package"], diff --git a/src/cdk/a11y/BUILD.bazel b/src/cdk/a11y/BUILD.bazel index ca7a61f0137c..67c417d2793e 100644 --- a/src/cdk/a11y/BUILD.bazel +++ b/src/cdk/a11y/BUILD.bazel @@ -22,7 +22,6 @@ ng_module( "//src/cdk/keycodes", "//src/cdk/observers", "//src/cdk/platform", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/cdk/a11y/_a11y.scss b/src/cdk/a11y/_a11y.scss index 35214a6759ed..0b2d77c17b0d 100644 --- a/src/cdk/a11y/_a11y.scss +++ b/src/cdk/a11y/_a11y.scss @@ -18,15 +18,39 @@ } } -// Applies styles for users in high contrast mode. Note that this only applies -// to Microsoft browsers. Chrome can be included by checking for the `html[hc]` -// attribute, however Chrome handles high contrast differently. -// -// @param target Which kind of high contrast setting to target. Defaults to `active`, can be -// `white-on-black` or `black-on-white`. -@mixin cdk-high-contrast($target: active) { +/// Emits the mixin's content nested under `$selector-context` if `$selector-context` +/// is non-empty. +/// @param selector-context The selector under which to nest the mixin's content. +@mixin _cdk-optionally-nest-content($selector-context) { + @if ($selector-context == '') { + @content; + } + @else { + #{$selector-context} { + @content; + } + } +} + +/// Applies styles for users in high contrast mode. Note that this only applies +/// to Microsoft browsers. Chrome can be included by checking for the `html[hc]` +/// attribute, however Chrome handles high contrast differently. +/// +/// @param target Which kind of high contrast setting to target. Defaults to `active`, can be +/// `white-on-black` or `black-on-white`. +/// @param encapsulation Whether to emit styles for view encapsulation. Values are: +/// * `on` - works for `Emulated`, `Native`, and `ShadowDom` +/// * `off` - works for `None` +/// * `any` - works for all encapsulation modes by emitting the CSS twice (default). +@mixin cdk-high-contrast($target: active, $encapsulation: 'any') { @if ($target != 'active' and $target != 'black-on-white' and $target != 'white-on-black') { - @error 'Unknown cdk-high-contrast value "#{$target}" provided. Allowed values are "active", "black-on-white", and "white-on-black"'; // stylelint-disable + @error 'Unknown cdk-high-contrast value "#{$target}" provided. ' + + 'Allowed values are "active", "black-on-white", and "white-on-black"'; + } + + @if ($encapsulation != 'on' and $encapsulation != 'off' and $encapsulation != 'any') { + @error 'Unknown cdk-high-contrast encapsulation "#{$encapsulation}" provided. ' + + 'Allowed values are "on", "off", and "any"'; } // If the selector context has multiple parts, such as `.section, .region`, just doing @@ -34,12 +58,19 @@ // context. We address this by nesting the selector context under .cdk-high-contrast. @at-root { $selector-context: #{&}; - .cdk-high-contrast-#{$target} { - @if ($selector-context == '') { - @content - } @else { - #{$selector-context} { - @content + + @if ($encapsulation != 'on') { + .cdk-high-contrast-#{$target} { + @include _cdk-optionally-nest-content($selector-context) { + @content; + } + } + } + + @if ($encapsulation != 'off') { + .cdk-high-contrast-#{$target} :host { + @include _cdk-optionally-nest-content($selector-context) { + @content; } } } diff --git a/src/cdk/a11y/a11y-module.ts b/src/cdk/a11y/a11y-module.ts index cf2b9f45be4f..1216bb8db95c 100644 --- a/src/cdk/a11y/a11y-module.ts +++ b/src/cdk/a11y/a11y-module.ts @@ -8,7 +8,6 @@ import {ObserversModule} from '@angular/cdk/observers'; import {PlatformModule} from '@angular/cdk/platform'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {CdkMonitorFocus} from './focus-monitor/focus-monitor'; import {CdkTrapFocus} from './focus-trap/focus-trap'; @@ -17,7 +16,7 @@ import {CdkAriaLive} from './live-announcer/live-announcer'; @NgModule({ - imports: [CommonModule, PlatformModule, ObserversModule], + imports: [PlatformModule, ObserversModule], declarations: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus], exports: [CdkAriaLive, CdkTrapFocus, CdkMonitorFocus], }) diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap-config.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap-config.ts new file mode 100644 index 000000000000..8e5ea67a533d --- /dev/null +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap-config.ts @@ -0,0 +1,11 @@ +/** + * @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 class ConfigurableFocusTrapConfig { + defer: boolean = false; + } diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts new file mode 100644 index 000000000000..33aae4ef9512 --- /dev/null +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts @@ -0,0 +1,54 @@ +/** + * @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 {DOCUMENT} from '@angular/common'; +import { + Inject, + Injectable, + Optional, + NgZone, +} from '@angular/core'; +import {InteractivityChecker} from '../interactivity-checker/interactivity-checker'; +import {ConfigurableFocusTrap} from './configurable-focus-trap'; +import {ConfigurableFocusTrapConfig} from './configurable-focus-trap-config'; +import {FOCUS_TRAP_INERT_STRATEGY, FocusTrapInertStrategy} from './focus-trap-inert-strategy'; +import {EventListenerFocusTrapInertStrategy} from './event-listener-inert-strategy'; +import {FocusTrapManager} from './focus-trap-manager'; + +/** Factory that allows easy instantiation of configurable focus traps. */ +@Injectable({providedIn: 'root'}) +export class ConfigurableFocusTrapFactory { + private _document: Document; + private _inertStrategy: FocusTrapInertStrategy; + + constructor( + private _checker: InteractivityChecker, + private _ngZone: NgZone, + private _focusTrapManager: FocusTrapManager, + @Inject(DOCUMENT) _document: any, + @Optional() @Inject(FOCUS_TRAP_INERT_STRATEGY) _inertStrategy?: FocusTrapInertStrategy) { + + this._document = _document; + // TODO split up the strategies into different modules, similar to DateAdapter. + this._inertStrategy = _inertStrategy || new EventListenerFocusTrapInertStrategy(); + } + + /** + * Creates a focus-trapped region around the given element. + * @param element The element around which focus will be trapped. + * @param deferCaptureElements Defers the creation of focus-capturing elements to be done + * manually by the user. + * @returns The created focus trap instance. + */ + create(element: HTMLElement, config: ConfigurableFocusTrapConfig = + new ConfigurableFocusTrapConfig()): ConfigurableFocusTrap { + return new ConfigurableFocusTrap( + element, this._checker, this._ngZone, this._document, this._focusTrapManager, + this._inertStrategy, config); + } +} diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts new file mode 100644 index 000000000000..c810714b4a8f --- /dev/null +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts @@ -0,0 +1,127 @@ +import {AfterViewInit, Component, ElementRef, Type, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + A11yModule, + ConfigurableFocusTrap, + ConfigurableFocusTrapFactory, + FOCUS_TRAP_INERT_STRATEGY, + FocusTrap, + FocusTrapInertStrategy +} from '../index'; +import {FocusTrapManager} from './focus-trap-manager'; + +describe('ConfigurableFocusTrap', () => { + let providers: Array; + + describe('with FocusTrapInertStrategy', () => { + let mockInertStrategy: FocusTrapInertStrategy; + + beforeEach(() => { + mockInertStrategy = new MockFocusTrapInertStrategy(); + providers = [{provide: FOCUS_TRAP_INERT_STRATEGY, useValue: mockInertStrategy}]; + }); + + it('Calls preventFocus when it is created', () => { + spyOn(mockInertStrategy, 'preventFocus'); + spyOn(mockInertStrategy, 'allowFocus'); + + const fixture = createComponent(SimpleFocusTrap, providers); + fixture.detectChanges(); + + expect(mockInertStrategy.preventFocus).toHaveBeenCalledTimes(1); + expect(mockInertStrategy.allowFocus).not.toHaveBeenCalled(); + }); + + it('Calls preventFocus when it is enabled', () => { + spyOn(mockInertStrategy, 'preventFocus'); + + const fixture = createComponent(SimpleFocusTrap, providers); + const componentInstance = fixture.componentInstance; + fixture.detectChanges(); + + componentInstance.focusTrap.enabled = true; + + expect(mockInertStrategy.preventFocus).toHaveBeenCalledTimes(2); + }); + + it('Calls allowFocus when it is disabled', () => { + spyOn(mockInertStrategy, 'allowFocus'); + + const fixture = createComponent(SimpleFocusTrap, providers); + const componentInstance = fixture.componentInstance; + fixture.detectChanges(); + + componentInstance.focusTrap.enabled = false; + + expect(mockInertStrategy.allowFocus).toHaveBeenCalledTimes(1); + }); + }); + + describe('with FocusTrapManager', () => { + let manager: FocusTrapManager; + + beforeEach(() => { + manager = new FocusTrapManager(); + providers = [{provide: FocusTrapManager, useValue: manager}]; + }); + + it('Registers when it is created', () => { + spyOn(manager, 'register'); + + const fixture = createComponent(SimpleFocusTrap, providers); + fixture.detectChanges(); + + expect(manager.register).toHaveBeenCalledTimes(1); + }); + + it('Deregisters when it is disabled', () => { + spyOn(manager, 'deregister'); + + const fixture = createComponent(SimpleFocusTrap, providers); + const componentInstance = fixture.componentInstance; + fixture.detectChanges(); + + componentInstance.focusTrap.enabled = false; + + expect(manager.deregister).toHaveBeenCalledTimes(1); + }); + }); +}); + +function createComponent(componentType: Type, providers: Array = [] + ): ComponentFixture { + TestBed.configureTestingModule({ + imports: [A11yModule], + declarations: [componentType], + providers: providers + }).compileComponents(); + + return TestBed.createComponent(componentType); + } + +@Component({ + template: ` +
+ + +
+ ` +}) +class SimpleFocusTrap implements AfterViewInit { + @ViewChild('focusTrapElement') focusTrapElement!: ElementRef; + + focusTrap: ConfigurableFocusTrap; + + constructor(private _focusTrapFactory: ConfigurableFocusTrapFactory) { + } + + ngAfterViewInit() { + this.focusTrap = this._focusTrapFactory.create(this.focusTrapElement.nativeElement); + } +} + +class MockFocusTrapInertStrategy implements FocusTrapInertStrategy { + preventFocus(focusTrap: FocusTrap) {} + + allowFocus(focusTrap: FocusTrap) {} +} diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap.ts new file mode 100644 index 000000000000..8d5c8f10fb13 --- /dev/null +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap.ts @@ -0,0 +1,63 @@ +/** + * @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 {NgZone} from '@angular/core'; +import {InteractivityChecker} from '../interactivity-checker/interactivity-checker'; +import {FocusTrap} from './focus-trap'; +import {FocusTrapManager, ManagedFocusTrap} from './focus-trap-manager'; +import {FocusTrapInertStrategy} from './focus-trap-inert-strategy'; +import {ConfigurableFocusTrapConfig} from './configurable-focus-trap-config'; + +/** + * Class that allows for trapping focus within a DOM element. + * + * This class uses a strategy pattern that determines how it traps focus. + * See FocusTrapInertStrategy. + */ +export class ConfigurableFocusTrap extends FocusTrap implements ManagedFocusTrap { + /** Whether the FocusTrap is enabled. */ + get enabled(): boolean { return this._enabled; } + set enabled(value: boolean) { + this._enabled = value; + if (this._enabled) { + this._focusTrapManager.register(this); + } else { + this._focusTrapManager.deregister(this); + } + } + + constructor( + _element: HTMLElement, + _checker: InteractivityChecker, + _ngZone: NgZone, + _document: Document, + private _focusTrapManager: FocusTrapManager, + private _inertStrategy: FocusTrapInertStrategy, + config: ConfigurableFocusTrapConfig) { + super(_element, _checker, _ngZone, _document, config.defer); + this._focusTrapManager.register(this); + } + + /** Notifies the FocusTrapManager that this FocusTrap will be destroyed. */ + destroy() { + this._focusTrapManager.deregister(this); + super.destroy(); + } + + /** @docs-private Implemented as part of ManagedFocusTrap. */ + _enable() { + this._inertStrategy.preventFocus(this); + this.toggleAnchors(true); + } + + /** @docs-private Implemented as part of ManagedFocusTrap. */ + _disable() { + this._inertStrategy.allowFocus(this); + this.toggleAnchors(false); + } +} diff --git a/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts new file mode 100644 index 000000000000..41039da403f9 --- /dev/null +++ b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts @@ -0,0 +1,96 @@ +import {AfterViewInit, Component, ElementRef, Type, ViewChild} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; +import {patchElementFocus} from '@angular/cdk/testing/private'; +import { + A11yModule, + ConfigurableFocusTrapFactory, + ConfigurableFocusTrap, + EventListenerFocusTrapInertStrategy, + FOCUS_TRAP_INERT_STRATEGY, +} from '../index'; + + +describe('EventListenerFocusTrapInertStrategy', () => { + const providers = [ + {provide: FOCUS_TRAP_INERT_STRATEGY, useValue: new EventListenerFocusTrapInertStrategy()}]; + + it('refocuses the first FocusTrap element when focus moves outside the FocusTrap', + fakeAsync(() => { + const fixture = createComponent(SimpleFocusTrap, providers); + const componentInstance = fixture.componentInstance; + fixture.detectChanges(); + + componentInstance.outsideFocusableElement.nativeElement.focus(); + flush(); + + expect(componentInstance.activeElement).toBe( + componentInstance.firstFocusableElement.nativeElement, + 'Expected first focusable element to be focused'); + })); + + it('does not intercept focus when focus moves to another element in the FocusTrap', + fakeAsync(() => { + const fixture = createComponent(SimpleFocusTrap, providers); + const componentInstance = fixture.componentInstance; + fixture.detectChanges(); + + componentInstance.secondFocusableElement.nativeElement.focus(); + flush(); + + expect(componentInstance.activeElement).toBe( + componentInstance.secondFocusableElement.nativeElement, + 'Expected second focusable element to be focused'); + })); +}); + +function createComponent(componentType: Type, providers: Array = [] + ): ComponentFixture { + TestBed.configureTestingModule({ + imports: [A11yModule], + declarations: [componentType], + providers: providers + }).compileComponents(); + + return TestBed.createComponent(componentType); + } + +@Component({ + template: ` + +
+ + +
+ ` +}) +class SimpleFocusTrap implements AfterViewInit { + @ViewChild('focusTrapElement') focusTrapElement!: ElementRef; + @ViewChild('outsideFocusable') outsideFocusableElement!: ElementRef; + @ViewChild('firstFocusable') firstFocusableElement!: ElementRef; + @ViewChild('secondFocusable') secondFocusableElement!: ElementRef; + + focusTrap: ConfigurableFocusTrap; + + // Since our custom stubbing in `patchElementFocus` won't update + // the `document.activeElement`, we need to keep track of it here. + activeElement: EventTarget | null; + + constructor(private _focusTrapFactory: ConfigurableFocusTrapFactory) { + } + + ngAfterViewInit() { + // Ensure consistent focus timing across browsers. + [ + this.focusTrapElement, + this.outsideFocusableElement, + this.firstFocusableElement, + this.secondFocusableElement + ].forEach(({nativeElement}) => { + patchElementFocus(nativeElement); + nativeElement.addEventListener('focus', event => this.activeElement = event.target); + }); + + this.focusTrap = this._focusTrapFactory.create(this.focusTrapElement.nativeElement); + this.focusTrap.focusFirstTabbableElementWhenReady(); + } +} diff --git a/src/cdk/a11y/focus-trap/event-listener-inert-strategy.ts b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.ts new file mode 100644 index 000000000000..4b9da49e81ce --- /dev/null +++ b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.ts @@ -0,0 +1,66 @@ +/** + * @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 {FocusTrapInertStrategy} from './focus-trap-inert-strategy'; +import {ConfigurableFocusTrap} from './configurable-focus-trap'; +import {closest} from './polyfill'; + +/** + * Lightweight FocusTrapInertStrategy that adds a document focus event + * listener to redirect focus back inside the FocusTrap. + */ +export class EventListenerFocusTrapInertStrategy implements FocusTrapInertStrategy { + /** Focus event handler. */ + private _listener: ((e: FocusEvent) => void) | null = null; + + /** Adds a document event listener that keeps focus inside the FocusTrap. */ + preventFocus(focusTrap: ConfigurableFocusTrap): void { + // Ensure there's only one listener per document + if (this._listener) { + focusTrap._document.removeEventListener('focus', this._listener!, true); + } + + this._listener = (e: FocusEvent) => this._trapFocus(focusTrap, e); + focusTrap._ngZone.runOutsideAngular(() => { + focusTrap._document.addEventListener('focus', this._listener!, true); + }); + } + + /** Removes the event listener added in preventFocus. */ + allowFocus(focusTrap: ConfigurableFocusTrap): void { + if (!this._listener) { + return; + } + focusTrap._document.removeEventListener('focus', this._listener!, true); + this._listener = null; + } + + /** + * Refocuses the first element in the FocusTrap if the focus event target was outside + * the FocusTrap. + * + * This is an event listener callback. The event listener is added in runOutsideAngular, + * so all this code runs outside Angular as well. + */ + private _trapFocus(focusTrap: ConfigurableFocusTrap, event: FocusEvent) { + const target = event.target as HTMLElement; + // Don't refocus if target was in an overlay, because the overlay might be associated + // with an element inside the FocusTrap, ex. mat-select. + if (!focusTrap._element.contains(target) && + closest(target, 'div.cdk-overlay-pane') === null) { + // Some legacy FocusTrap usages have logic that focuses some element on the page + // just before FocusTrap is destroyed. For backwards compatibility, wait + // to be sure FocusTrap is still enabled before refocusing. + setTimeout(() => { + if (focusTrap.enabled) { + focusTrap.focusFirstTabbableElement(); + } + }); + } + } +} diff --git a/src/cdk/a11y/focus-trap/focus-trap-inert-strategy.ts b/src/cdk/a11y/focus-trap/focus-trap-inert-strategy.ts new file mode 100644 index 000000000000..137fb75cdfe1 --- /dev/null +++ b/src/cdk/a11y/focus-trap/focus-trap-inert-strategy.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 { + InjectionToken, +} from '@angular/core'; +import {FocusTrap} from './focus-trap'; + +/** The injection token used to specify the inert strategy. */ +export const FOCUS_TRAP_INERT_STRATEGY = + new InjectionToken('FOCUS_TRAP_INERT_STRATEGY'); + +/** + * A strategy that dictates how FocusTrap should prevent elements + * outside of the FocusTrap from being focused. + */ +export interface FocusTrapInertStrategy { + /** Makes all elements outside focusTrap unfocusable. */ + preventFocus(focusTrap: FocusTrap): void; + /** Reverts elements made unfocusable by preventFocus to their previous state. */ + allowFocus(focusTrap: FocusTrap): void; +} diff --git a/src/cdk/a11y/focus-trap/focus-trap-manager.spec.ts b/src/cdk/a11y/focus-trap/focus-trap-manager.spec.ts new file mode 100644 index 000000000000..f972e33f9e50 --- /dev/null +++ b/src/cdk/a11y/focus-trap/focus-trap-manager.spec.ts @@ -0,0 +1,48 @@ +import {FocusTrapManager, ManagedFocusTrap} from './focus-trap-manager'; + +describe('FocusTrapManager', () => { + let manager: FocusTrapManager; + + beforeEach(() => { + manager = new FocusTrapManager(); + }); + + it('Enables a FocusTrap when it is registered', () => { + const focusTrap = new MockManagedFocusTrap(); + spyOn(focusTrap, '_enable'); + manager.register(focusTrap); + expect(focusTrap._enable).toHaveBeenCalledTimes(1); + }); + + it('Disables a FocusTrap when it is deregistered', () => { + const focusTrap = new MockManagedFocusTrap(); + spyOn(focusTrap, '_disable'); + manager.deregister(focusTrap); + expect(focusTrap._disable).toHaveBeenCalledTimes(1); + }); + + it('Disables the previous FocusTrap when a new FocusTrap is registered', () => { + const focusTrap1 = new MockManagedFocusTrap(); + const focusTrap2 = new MockManagedFocusTrap(); + spyOn(focusTrap1, '_disable'); + manager.register(focusTrap1); + manager.register(focusTrap2); + expect(focusTrap1._disable).toHaveBeenCalledTimes(1); + }); + + it('Filters duplicates before registering a new FocusTrap', () => { + const focusTrap = new MockManagedFocusTrap(); + spyOn(focusTrap, '_disable'); + manager.register(focusTrap); + manager.register(focusTrap); + expect(focusTrap._disable).not.toHaveBeenCalled(); + }); +}); + +class MockManagedFocusTrap implements ManagedFocusTrap { + _enable() {} + _disable() {} + focusInitialElementWhenReady(): Promise { + return Promise.resolve(true); + } +} diff --git a/src/cdk/a11y/focus-trap/focus-trap-manager.ts b/src/cdk/a11y/focus-trap/focus-trap-manager.ts new file mode 100644 index 000000000000..213a10c70f61 --- /dev/null +++ b/src/cdk/a11y/focus-trap/focus-trap-manager.ts @@ -0,0 +1,63 @@ +/** + * @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 {Injectable} from '@angular/core'; + +/** + * A FocusTrap managed by FocusTrapManager. + * Implemented by ConfigurableFocusTrap to avoid circular dependency. + */ +export interface ManagedFocusTrap { + _enable(): void; + _disable(): void; + focusInitialElementWhenReady(): Promise; +} + +/** Injectable that ensures only the most recently enabled FocusTrap is active. */ +@Injectable({providedIn: 'root'}) +export class FocusTrapManager { + // A stack of the FocusTraps on the page. Only the FocusTrap at the + // top of the stack is active. + private _focusTrapStack: ManagedFocusTrap[] = []; + + /** + * Disables the FocusTrap at the top of the stack, and then pushes + * the new FocusTrap onto the stack. + */ + register(focusTrap: ManagedFocusTrap): void { + // Dedupe focusTraps that register multiple times. + this._focusTrapStack = this._focusTrapStack.filter((ft) => ft !== focusTrap); + + let stack = this._focusTrapStack; + + if (stack.length) { + stack[stack.length - 1]._disable(); + } + + stack.push(focusTrap); + focusTrap._enable(); + } + + /** + * Removes the FocusTrap from the stack, and activates the + * FocusTrap that is the new top of the stack. + */ + deregister(focusTrap: ManagedFocusTrap): void { + focusTrap._disable(); + + const stack = this._focusTrapStack; + + const i = stack.indexOf(focusTrap); + if (i !== -1) { + stack.splice(i, 1); + if (stack.length) { + stack[stack.length - 1]._enable(); + } + } + } +} diff --git a/src/cdk/a11y/focus-trap/focus-trap.ts b/src/cdk/a11y/focus-trap/focus-trap.ts index 3ff81bad43fe..ccd2f5fb2b8d 100644 --- a/src/cdk/a11y/focus-trap/focus-trap.ts +++ b/src/cdk/a11y/focus-trap/focus-trap.ts @@ -30,6 +30,9 @@ import {InteractivityChecker} from '../interactivity-checker/interactivity-check * This class currently uses a relatively simple approach to focus trapping. * It assumes that the tab order is the same as DOM order, which is not necessarily true. * Things like `tabIndex > 0`, flex `order`, and shadow roots can cause to two to misalign. + * + * @deprecated Use `ConfigurableFocusTrap` instead. + * @breaking-change for 11.0.0 Remove this class. */ export class FocusTrap { private _startAnchor: HTMLElement | null; @@ -50,13 +53,13 @@ export class FocusTrap { this._toggleAnchorTabIndex(value, this._endAnchor); } } - private _enabled: boolean = true; + protected _enabled: boolean = true; constructor( - private _element: HTMLElement, + readonly _element: HTMLElement, private _checker: InteractivityChecker, - private _ngZone: NgZone, - private _document: Document, + readonly _ngZone: NgZone, + readonly _document: Document, deferAnchors = false) { if (!deferAnchors) { @@ -319,6 +322,17 @@ export class FocusTrap { isEnabled ? anchor.setAttribute('tabindex', '0') : anchor.removeAttribute('tabindex'); } + /** + * Toggles the`tabindex` of both anchors to either trap Tab focus or allow it to escape. + * @param enabled: Whether the anchors should trap Tab. + */ + protected toggleAnchors(enabled: boolean) { + if (this._startAnchor && this._endAnchor) { + this._toggleAnchorTabIndex(enabled, this._startAnchor); + this._toggleAnchorTabIndex(enabled, this._endAnchor); + } + } + /** Executes a function when the zone is stable. */ private _executeOnStable(fn: () => any): void { if (this._ngZone.isStable) { @@ -329,8 +343,11 @@ export class FocusTrap { } } - -/** Factory that allows easy instantiation of focus traps. */ +/** + * Factory that allows easy instantiation of focus traps. + * @deprecated Use `ConfigurableFocusTrapFactory` instead. + * @breaking-change for 11.0.0 Remove this class. + */ @Injectable({providedIn: 'root'}) export class FocusTrapFactory { private _document: Document; diff --git a/src/cdk/a11y/focus-trap/polyfill.ts b/src/cdk/a11y/focus-trap/polyfill.ts new file mode 100644 index 000000000000..cd89b3900251 --- /dev/null +++ b/src/cdk/a11y/focus-trap/polyfill.ts @@ -0,0 +1,40 @@ +/** + * @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 + */ + +/** IE 11 compatible closest implementation that is able to start from non-Element Nodes. */ +export function closest(element: EventTarget|Element|null|undefined, selector: string): + Element|null { + if (!(element instanceof Node)) { return null; } + + let curr: Node|null = element; + while (curr != null && !(curr instanceof Element)) { + curr = curr.parentNode; + } + + return curr && (hasNativeClosest ? + curr.closest(selector) : polyfillClosest(curr, selector)) as Element|null; +} + +/** Polyfill for browsers without Element.closest. */ +function polyfillClosest(element: Element, selector: string): Element|null { + let curr: Node|null = element; + while (curr != null && !(curr instanceof Element && matches(curr, selector))) { + curr = curr.parentNode; + } + + return (curr || null) as Element|null; +} + +const hasNativeClosest = typeof Element != 'undefined' && !!Element.prototype.closest; + +/** IE 11 compatible matches implementation. */ +function matches(element: Element, selector: string): boolean { + return element.matches ? + element.matches(selector) : + (element as any)['msMatchesSelector'](selector); +} diff --git a/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts b/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts index 2d9953c919cd..02801cc76b98 100644 --- a/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts +++ b/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts @@ -390,7 +390,7 @@ describe('InteractivityChecker', () => { appendElements([objectEl]); - // This is a hack to create an empty contentDocument for the frame element. + // This creates an empty contentDocument for the frame element. objectEl.type = 'text/html'; objectEl.contentDocument!.body.appendChild(button); diff --git a/src/cdk/a11y/key-manager/list-key-manager.spec.ts b/src/cdk/a11y/key-manager/list-key-manager.spec.ts index 3915cdc4794d..370bb8d96c04 100644 --- a/src/cdk/a11y/key-manager/list-key-manager.spec.ts +++ b/src/cdk/a11y/key-manager/list-key-manager.spec.ts @@ -33,7 +33,9 @@ class FakeQueryList extends QueryList { set length(_) { /* Empty setter for base class constructor */ } get first() { return this.items[0]; } toArray() { return this.items; } - some() { return this.items.some.apply(this.items, arguments); } + some(...args: [(value: T, index: number, array: T[]) => unknown, any?]) { + return this.items.some(...args); + } notifyOnChanges() { this.changes.next(this); } } diff --git a/src/cdk/a11y/public-api.ts b/src/cdk/a11y/public-api.ts index 26df94b5275e..cb70ff27f286 100644 --- a/src/cdk/a11y/public-api.ts +++ b/src/cdk/a11y/public-api.ts @@ -9,7 +9,11 @@ export * from './aria-describer/aria-describer'; export * from './key-manager/activedescendant-key-manager'; export * from './key-manager/focus-key-manager'; export * from './key-manager/list-key-manager'; +export * from './focus-trap/configurable-focus-trap'; +export * from './focus-trap/event-listener-inert-strategy'; export * from './focus-trap/focus-trap'; +export * from './focus-trap/configurable-focus-trap-factory'; +export * from './focus-trap/focus-trap-inert-strategy'; export * from './interactivity-checker/interactivity-checker'; export * from './live-announcer/live-announcer'; export * from './live-announcer/live-announcer-tokens'; diff --git a/src/cdk/clipboard/BUILD.bazel b/src/cdk/clipboard/BUILD.bazel index 5501c9b33d07..08d779bf7ccf 100644 --- a/src/cdk/clipboard/BUILD.bazel +++ b/src/cdk/clipboard/BUILD.bazel @@ -11,7 +11,6 @@ ng_module( assets = glob(["**/*.html"]), module_name = "@angular/cdk/clipboard", deps = [ - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/cdk/clipboard/clipboard-module.ts b/src/cdk/clipboard/clipboard-module.ts index f09394509aac..314342533ee3 100644 --- a/src/cdk/clipboard/clipboard-module.ts +++ b/src/cdk/clipboard/clipboard-module.ts @@ -6,14 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {CdkCopyToClipboard} from './copy-to-clipboard'; @NgModule({ declarations: [CdkCopyToClipboard], - imports: [CommonModule], exports: [CdkCopyToClipboard], }) export class ClipboardModule { diff --git a/src/cdk/clipboard/copy-to-clipboard.spec.ts b/src/cdk/clipboard/copy-to-clipboard.spec.ts index 9a3edccf031f..64893bff01f4 100644 --- a/src/cdk/clipboard/copy-to-clipboard.spec.ts +++ b/src/cdk/clipboard/copy-to-clipboard.spec.ts @@ -59,7 +59,7 @@ describe('CdkCopyToClipboard', () => { it('emits copied event false when copy fails', fakeAsync(() => { spyOn(clipboard, 'copy').and.returnValue(false); fixture.nativeElement.querySelector('button')!.click(); - tick(); + tick(1); expect(fixture.componentInstance.copied).toHaveBeenCalledWith(false); })); @@ -76,7 +76,7 @@ describe('CdkCopyToClipboard', () => { fixture.nativeElement.querySelector('button')!.click(); fixture.detectChanges(); - tick(); + tick(3); expect(attempts).toBe(maxAttempts); expect(fixture.componentInstance.copied).toHaveBeenCalledTimes(1); @@ -98,10 +98,37 @@ describe('CdkCopyToClipboard', () => { fixture.nativeElement.querySelector('button')!.click(); fixture.detectChanges(); - tick(); + tick(3); expect(attempts).toBe(maxAttempts); expect(fixture.componentInstance.copied).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.copied).toHaveBeenCalledWith(false); })); + + it('should destroy any pending copies when the directive is destroyed', fakeAsync(() => { + const fakeCopy = { + copy: jasmine.createSpy('copy spy').and.returnValue(false) as () => boolean, + destroy: jasmine.createSpy('destroy spy') as () => void + } as PendingCopy; + + fixture.componentInstance.attempts = 10; + fixture.detectChanges(); + + spyOn(clipboard, 'beginCopy').and.returnValue(fakeCopy); + fixture.detectChanges(); + + fixture.nativeElement.querySelector('button')!.click(); + fixture.detectChanges(); + tick(1); + + expect(fakeCopy.copy).toHaveBeenCalledTimes(2); + expect(fakeCopy.destroy).toHaveBeenCalledTimes(0); + + fixture.destroy(); + tick(1); + + expect(fakeCopy.copy).toHaveBeenCalledTimes(2); + expect(fakeCopy.destroy).toHaveBeenCalledTimes(1); + })); + }); diff --git a/src/cdk/clipboard/copy-to-clipboard.ts b/src/cdk/clipboard/copy-to-clipboard.ts index 9a6f90560a5c..4f63fdc27d25 100644 --- a/src/cdk/clipboard/copy-to-clipboard.ts +++ b/src/cdk/clipboard/copy-to-clipboard.ts @@ -15,8 +15,10 @@ import { InjectionToken, Inject, Optional, + OnDestroy, } from '@angular/core'; import {Clipboard} from './clipboard'; +import {PendingCopy} from './pending-copy'; /** Object that can be used to configure the default options for `CdkCopyToClipboard`. */ export interface CdkCopyToClipboardConfig { @@ -38,7 +40,7 @@ export const CKD_COPY_TO_CLIPBOARD_CONFIG = '(click)': 'copy()', } }) -export class CdkCopyToClipboard { +export class CdkCopyToClipboard implements OnDestroy { /** Content to be copied. */ @Input('cdkCopyToClipboard') text: string = ''; @@ -62,6 +64,15 @@ export class CdkCopyToClipboard { */ @Output('copied') _deprecatedCopied = this.copied; + /** Copies that are currently being attempted. */ + private _pending = new Set(); + + /** Whether the directive has been destroyed. */ + private _destroyed: boolean; + + /** Timeout for the current copy attempt. */ + private _currentTimeout: any; + constructor( private _clipboard: Clipboard, /** @@ -81,16 +92,21 @@ export class CdkCopyToClipboard { if (attempts > 1) { let remainingAttempts = attempts; const pending = this._clipboard.beginCopy(this.text); + this._pending.add(pending); + const attempt = () => { const successful = pending.copy(); - if (!successful && --remainingAttempts) { + if (!successful && --remainingAttempts && !this._destroyed) { // @breaking-change 10.0.0 Remove null check for `_ngZone`. if (this._ngZone) { - this._ngZone.runOutsideAngular(() => setTimeout(attempt)); + this._currentTimeout = this._ngZone.runOutsideAngular(() => setTimeout(attempt, 1)); } else { - setTimeout(attempt); + // We use 1 for the timeout since it's more predictable when flushing in unit tests. + this._currentTimeout = setTimeout(attempt, 1); } } else { + this._currentTimeout = null; + this._pending.delete(pending); pending.destroy(); this.copied.emit(successful); } @@ -100,4 +116,14 @@ export class CdkCopyToClipboard { this.copied.emit(this._clipboard.copy(this.text)); } } + + ngOnDestroy() { + if (this._currentTimeout) { + clearTimeout(this._currentTimeout); + } + + this._pending.forEach(copy => copy.destroy()); + this._pending.clear(); + this._destroyed = true; + } } diff --git a/src/cdk/collections/array-data-source.ts b/src/cdk/collections/array-data-source.ts index 4f54759108c6..04c2957926c4 100644 --- a/src/cdk/collections/array-data-source.ts +++ b/src/cdk/collections/array-data-source.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Observable, of as observableOf} from 'rxjs'; +import {Observable, isObservable, of as observableOf} from 'rxjs'; import {DataSource} from './data-source'; @@ -17,7 +17,7 @@ export class ArrayDataSource extends DataSource { } connect(): Observable> { - return this._data instanceof Observable ? this._data : observableOf(this._data); + return isObservable(this._data) ? this._data : observableOf(this._data); } disconnect() {} diff --git a/src/cdk/drag-drop/directives/config.ts b/src/cdk/drag-drop/directives/config.ts new file mode 100644 index 000000000000..7ab2b217625d --- /dev/null +++ b/src/cdk/drag-drop/directives/config.ts @@ -0,0 +1,54 @@ +/** + * @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 {InjectionToken} from '@angular/core'; +import {DragRefConfig, Point, DragRef} from '../drag-ref'; + +/** Possible values that can be used to configure the drag start delay. */ +export type DragStartDelay = number | {touch: number, mouse: number}; + +/** Possible axis along which dragging can be locked. */ +export type DragAxis = 'x' | 'y'; + +/** Function that can be used to constrain the position of a dragged element. */ +export type DragConstrainPosition = (point: Point, dragRef: DragRef) => Point; + +/** Possible orientations for a drop list. */ +export type DropListOrientation = 'horizontal' | 'vertical'; + +/** + * Injection token that can be used to configure the + * behavior of the drag&drop-related components. + */ +export const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG'); + +/** + * Object that can be used to configure the drag + * items and drop lists within a module or a component. + */ +export interface DragDropConfig extends Partial { + lockAxis?: DragAxis; + dragStartDelay?: DragStartDelay; + constrainPosition?: DragConstrainPosition; + previewClass?: string | string[]; + boundaryElement?: string; + rootElementSelector?: string; + draggingDisabled?: boolean; + sortingDisabled?: boolean; + listAutoScrollDisabled?: boolean; + listOrientation?: DropListOrientation; +} + +/** + * @deprecated No longer being used. To be removed. + * @breaking-change 10.0.0 + * @docs-private + */ +export function CDK_DRAG_CONFIG_FACTORY(): DragDropConfig { + return {dragStartThreshold: 5, pointerDirectionChangeThreshold: 5}; +} diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index dcac4f49d05a..1aa2adf09852 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -23,17 +23,18 @@ import { } from '@angular/core'; import {TestBed, ComponentFixture, fakeAsync, flush, tick} from '@angular/core/testing'; import {DOCUMENT} from '@angular/common'; -import {ViewportRuler} from '@angular/cdk/scrolling'; +import {ViewportRuler, ScrollingModule} from '@angular/cdk/scrolling'; import {_supportsShadowDom} from '@angular/cdk/platform'; import {of as observableOf} from 'rxjs'; import {DragDropModule} from '../drag-drop-module'; import {CdkDragDrop, CdkDragEnter} from '../drag-events'; -import {DragRefConfig, Point, DragRef} from '../drag-ref'; +import {Point, DragRef} from '../drag-ref'; import {extendStyles} from '../drag-styling'; import {moveItemInArray} from '../drag-utils'; -import {CDK_DRAG_CONFIG, CdkDrag} from './drag'; +import {CdkDrag} from './drag'; +import {CDK_DRAG_CONFIG, DragDropConfig} from './config'; import {CdkDragHandle} from './drag-handle'; import {CdkDropList} from './drop-list'; import {CdkDropListGroup} from './drop-list-group'; @@ -47,7 +48,7 @@ describe('CdkDrag', () => { extraDeclarations: Type[] = []): ComponentFixture { TestBed .configureTestingModule({ - imports: [DragDropModule], + imports: [DragDropModule, ScrollingModule], declarations: [componentType, PassthroughComponent, ...extraDeclarations], providers: [ { @@ -58,7 +59,7 @@ describe('CdkDrag', () => { // have to deal with thresholds. dragStartThreshold: dragDistance, pointerDirectionChangeThreshold: 5 - } as DragRefConfig + } as DragDropConfig }, ...providers ], @@ -1132,6 +1133,60 @@ describe('CdkDrag', () => { subscription.unsubscribe(); })); + it('should prevent the default `mousemove` action even before the drag threshold has ' + + 'been reached', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable, [], 5); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dispatchMouseEvent(dragElement, 'mousedown', 2, 2); + fixture.detectChanges(); + const mousemoveEvent = dispatchMouseEvent(document, 'mousemove', 2, 2); + fixture.detectChanges(); + + expect(mousemoveEvent.defaultPrevented).toBe(true); + })); + + it('should prevent the default `touchmove` action even before the drag threshold has ' + + 'been reached', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable, [], 5); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dispatchTouchEvent(dragElement, 'touchstart', 2, 2); + fixture.detectChanges(); + const touchmoveEvent = dispatchTouchEvent(document, 'touchmove', 2, 2); + fixture.detectChanges(); + + expect(touchmoveEvent.defaultPrevented).toBe(true); + })); + + it('should be able to configure the drag input defaults through a provider', fakeAsync(() => { + const config: DragDropConfig = { + draggingDisabled: true, + dragStartDelay: 1337, + lockAxis: 'y', + constrainPosition: () => ({x: 1337, y: 42}), + previewClass: 'custom-preview-class', + boundaryElement: '.boundary', + rootElementSelector: '.root' + }; + + const fixture = createComponent(PlainStandaloneDraggable, [{ + provide: CDK_DRAG_CONFIG, + useValue: config + }]); + fixture.detectChanges(); + const drag = fixture.componentInstance.dragInstance; + expect(drag.disabled).toBe(true); + expect(drag.dragStartDelay).toBe(1337); + expect(drag.lockAxis).toBe('y'); + expect(drag.constrainPosition).toBe(config.constrainPosition); + expect(drag.previewClass).toBe('custom-preview-class'); + expect(drag.boundaryElement).toBe('.boundary'); + expect(drag.rootElementSelector).toBe('.root'); + })); + }); describe('draggable with a handle', () => { @@ -1169,6 +1224,18 @@ describe('CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); })); + it('should not be able to drag the element if the handle is disabled before init', + fakeAsync(() => { + const fixture = createComponent(StandaloneDraggableWithPreDisabledHandle); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + const handle = fixture.componentInstance.handleElement.nativeElement; + + expect(dragElement.style.transform).toBeFalsy(); + dragElementViaMouse(fixture, handle, 50, 100); + expect(dragElement.style.transform).toBeFalsy(); + })); + it('should not be able to drag using the handle if the element is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); @@ -1842,15 +1909,24 @@ describe('CdkDrag', () => { body: document.body, fullscreenElement: document.createElement('div'), ELEMENT_NODE: Node.ELEMENT_NODE, - querySelectorAll: function() { - return document.querySelectorAll.apply(document, arguments); + querySelectorAll: function(...args: [string]) { + return document.querySelectorAll(...args); + }, + addEventListener: function(...args: [ + string, + EventListenerOrEventListenerObject, + (boolean | AddEventListenerOptions | undefined)? + ]) { + document.addEventListener(...args); }, - addEventListener: function() { - document.addEventListener.apply(document, arguments); + removeEventListener: function(...args: [ + string, + EventListenerOrEventListenerObject, + (boolean | AddEventListenerOptions | undefined)? + ]) { + document.addEventListener(...args); }, - removeEventListener: function() { - document.addEventListener.apply(document, arguments); - } + createComment: (text: string) => document.createComment(text) }; const fixture = createComponent(DraggableInDropZone, [{ provide: DOCUMENT, @@ -3375,6 +3451,24 @@ describe('CdkDrag', () => { cleanup(); })); + it('should be able to auto-scroll a parent container', fakeAsync(() => { + const fixture = createComponent(DraggableInScrollableParentContainer); + fixture.detectChanges(); + const item = fixture.componentInstance.dragItems.first.element.nativeElement; + const container = fixture.nativeElement.querySelector('.container'); + const containerRect = container.getBoundingClientRect(); + + expect(container.scrollTop).toBe(0); + + startDraggingViaMouse(fixture, item); + dispatchMouseEvent(document, 'mousemove', + containerRect.left + containerRect.width / 2, containerRect.top + containerRect.height); + fixture.detectChanges(); + tickAnimationFrames(20); + + expect(container.scrollTop).toBeGreaterThan(0); + })); + it('should pick up descendants inside of containers', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithContainer); fixture.detectChanges(); @@ -3456,6 +3550,80 @@ describe('CdkDrag', () => { expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())) .toEqual(['One', 'Two', 'Zero', 'Three']); })); + + it('should be able to configure the drop input defaults through a provider', fakeAsync(() => { + const config: DragDropConfig = { + draggingDisabled: true, + sortingDisabled: true, + listAutoScrollDisabled: true, + listOrientation: 'horizontal', + lockAxis: 'y' + }; + + const fixture = createComponent(PlainStandaloneDropList, [{ + provide: CDK_DRAG_CONFIG, + useValue: config + }]); + fixture.detectChanges(); + const list = fixture.componentInstance.dropList; + expect(list.disabled).toBe(true); + expect(list.sortingDisabled).toBe(true); + expect(list.autoScrollDisabled).toBe(true); + expect(list.orientation).toBe('horizontal'); + expect(list.lockAxis).toBe('y'); + })); + + it('should disable scroll snapping while the user is dragging', fakeAsync(() => { + const fixture = createComponent(DraggableInDropZone); + fixture.detectChanges(); + const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; + const styles: any = fixture.componentInstance.dropInstance.element.nativeElement.style; + + // This test only applies to browsers that support scroll snapping. + if (!('scrollSnapType' in styles) && !('msScrollSnapType' in styles)) { + return; + } + + expect(styles.scrollSnapType || styles.msScrollSnapType).toBeFalsy(); + + startDraggingViaMouse(fixture, item); + + expect(styles.scrollSnapType || styles.msScrollSnapType).toBe('none'); + + dispatchMouseEvent(document, 'mouseup'); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(styles.scrollSnapType || styles.msScrollSnapType).toBeFalsy(); + })); + + it('should restore the previous inline scroll snap value', fakeAsync(() => { + const fixture = createComponent(DraggableInDropZone); + fixture.detectChanges(); + const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; + const styles: any = fixture.componentInstance.dropInstance.element.nativeElement.style; + + // This test only applies to browsers that support scroll snapping. + if (!('scrollSnapType' in styles) && !('msScrollSnapType' in styles)) { + return; + } + + styles.scrollSnapType = styles.msScrollSnapType = 'block'; + expect(styles.scrollSnapType || styles.msScrollSnapType).toBe('block'); + + startDraggingViaMouse(fixture, item); + + expect(styles.scrollSnapType || styles.msScrollSnapType).toBe('none'); + + dispatchMouseEvent(document, 'mouseup'); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(styles.scrollSnapType || styles.msScrollSnapType).toBe('block'); + })); + }); describe('in a connected drop container', () => { @@ -4403,6 +4571,31 @@ describe('CdkDrag', () => { }); })); + it('should not throw if its next sibling is removed while dragging', fakeAsync(() => { + const fixture = createComponent(ConnectedDropZonesWithSingleItems); + fixture.detectChanges(); + + const items = fixture.componentInstance.dragItems.toArray(); + const item = items[0]; + const nextSibling = items[1].element.nativeElement; + const extraSibling = document.createElement('div'); + const targetRect = nextSibling.getBoundingClientRect(); + + // Manually insert an element after the node to simulate an external package. + nextSibling.parentNode!.insertBefore(extraSibling, nextSibling); + + dragElementViaMouse(fixture, item.element.nativeElement, + targetRect.left + 1, targetRect.top + 1); + + // Remove the extra node after the element was dropped, but before the animation is over. + extraSibling.parentNode!.removeChild(extraSibling); + + expect(() => { + flush(); + fixture.detectChanges(); + }).not.toThrow(); + })); + }); describe('with nested drags', () => { @@ -4528,6 +4721,25 @@ class StandaloneDraggableWithHandle { @ViewChild(CdkDragHandle) handleInstance: CdkDragHandle; } +@Component({ + template: ` +
+
+
+ ` +}) +class StandaloneDraggableWithPreDisabledHandle { + @ViewChild('dragElement', {static: false}) dragElement: ElementRef; + @ViewChild('handleElement', {static: false}) handleElement: ElementRef; + @ViewChild(CdkDrag, {static: false}) dragInstance: CdkDrag; + disableHandle = true; +} + @Component({ template: `
' + DROP_ZONE_FIXTURE_TEMPLATE + '
', + + // Note that it needs a margin to ensure that it's not flush against the viewport + // edge which will cause the viewport to scroll, rather than the list. + styles: [` + .container { + max-height: 200px; + overflow: auto; + margin: 10vw 0 0 10vw; + } + `] +}) +class DraggableInScrollableParentContainer extends DraggableInDropZone { + constructor() { + super(); + + for (let i = 0; i < 60; i++) { + this.items.push({value: `Extra item ${i}`, height: ITEM_HEIGHT, margin: 0}); + } + } +} + + @Component({ // Note that we need the blank `ngSwitch` below to hit the code path that we're testing. template: ` @@ -5267,6 +5503,20 @@ class NestedDropZones { items = ['Zero', 'One', 'Two', 'Three']; } +@Component({ + template: `
` +}) +class PlainStandaloneDraggable { + @ViewChild(CdkDrag) dragInstance: CdkDrag; +} + +@Component({ + template: `
` +}) +class PlainStandaloneDropList { + @ViewChild(CdkDropList) dropList: CdkDropList; +} + /** * Drags an element to a position on the page using the mouse. * @param fixture Fixture on which to run change detection. diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index 05ed967b8682..e5dddd3331b1 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -51,9 +51,10 @@ import {CdkDragHandle} from './drag-handle'; import {CdkDragPlaceholder} from './drag-placeholder'; import {CdkDragPreview} from './drag-preview'; import {CDK_DRAG_PARENT} from '../drag-parent'; -import {DragRef, DragRefConfig, Point} from '../drag-ref'; +import {DragRef, Point} from '../drag-ref'; import {CdkDropListInternal as CdkDropList} from './drop-list'; import {DragDrop} from '../drag-drop'; +import {CDK_DRAG_CONFIG, DragDropConfig, DragStartDelay, DragAxis} from './config'; /** * Injection token that is used to provide a CdkDropList instance to CdkDrag. @@ -61,17 +62,6 @@ import {DragDrop} from '../drag-drop'; */ export const CDK_DROP_LIST = new InjectionToken('CDK_DROP_LIST'); -/** Injection token that can be used to configure the behavior of `CdkDrag`. */ -export const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG', { - providedIn: 'root', - factory: CDK_DRAG_CONFIG_FACTORY -}); - -/** @docs-private */ -export function CDK_DRAG_CONFIG_FACTORY(): DragRefConfig { - return {dragStartThreshold: 5, pointerDirectionChangeThreshold: 5}; -} - /** Element that can be moved inside a CdkDropList container. */ @Directive({ selector: '[cdkDrag]', @@ -102,7 +92,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { @Input('cdkDragData') data: T; /** Locks the position of the dragged element along the specified axis. */ - @Input('cdkDragLockAxis') lockAxis: 'x' | 'y'; + @Input('cdkDragLockAxis') lockAxis: DragAxis; /** * Selector that will be used to determine the root draggable element, starting from @@ -123,7 +113,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { * Amount of milliseconds to wait after the user has put their * pointer down before starting to drag the element. */ - @Input('cdkDragStartDelay') dragStartDelay: number | {touch: number, mouse: number} = 0; + @Input('cdkDragStartDelay') dragStartDelay: DragStartDelay; /** * Sets the position of a `CdkDrag` that is outside of a drop container. @@ -140,7 +130,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { this._disabled = coerceBooleanProperty(value); this._dragRef.disabled = this._disabled; } - private _disabled = false; + private _disabled: boolean; /** * Function that can be used to customize the logic of how the position of the drag item @@ -200,11 +190,22 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { /** Droppable container that the draggable is a part of. */ @Inject(CDK_DROP_LIST) @Optional() @SkipSelf() public dropContainer: CdkDropList, @Inject(DOCUMENT) private _document: any, private _ngZone: NgZone, - private _viewContainerRef: ViewContainerRef, @Inject(CDK_DRAG_CONFIG) config: DragRefConfig, + private _viewContainerRef: ViewContainerRef, + @Optional() @Inject(CDK_DRAG_CONFIG) config: DragDropConfig, @Optional() private _dir: Directionality, dragDrop: DragDrop, private _changeDetectorRef: ChangeDetectorRef) { - this._dragRef = dragDrop.createDrag(element, config); + this._dragRef = dragDrop.createDrag(element, { + dragStartThreshold: config && config.dragStartThreshold != null ? + config.dragStartThreshold : 5, + pointerDirectionChangeThreshold: config && config.pointerDirectionChangeThreshold != null ? + config.pointerDirectionChangeThreshold : 5 + }); this._dragRef.data = this; + + if (config) { + this._assignDefaults(config); + } + this._syncInputs(this._dragRef); this._handleEvents(this._dragRef); } @@ -256,7 +257,9 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { }), // Listen if the state of any of the handles changes. switchMap((handles: QueryList) => { - return merge(...handles.map(item => item._stateChanges)) as Observable; + return merge(...handles.map(item => { + return item._stateChanges.pipe(startWith(item)); + })) as Observable; }), takeUntil(this._destroyed) ).subscribe(handleInstance => { @@ -414,6 +417,37 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { }); } + /** Assigns the default input values based on a provided config object. */ + private _assignDefaults(config: DragDropConfig) { + const { + lockAxis, dragStartDelay, constrainPosition, previewClass, + boundaryElement, draggingDisabled, rootElementSelector + } = config; + + this.disabled = draggingDisabled == null ? false : draggingDisabled; + this.dragStartDelay = dragStartDelay || 0; + + if (lockAxis) { + this.lockAxis = lockAxis; + } + + if (constrainPosition) { + this.constrainPosition = constrainPosition; + } + + if (previewClass) { + this.previewClass = previewClass; + } + + if (boundaryElement) { + this.boundaryElement = boundaryElement; + } + + if (rootElementSelector) { + this.rootElementSelector = rootElementSelector; + } + } + static ngAcceptInputType_disabled: BooleanInput; } diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index ecde4757033c..0c89bb126a08 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -20,14 +20,17 @@ import { ChangeDetectorRef, SkipSelf, AfterContentInit, + Inject, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; +import {ScrollDispatcher} from '@angular/cdk/scrolling'; import {CdkDrag, CDK_DROP_LIST} from './drag'; import {CdkDragDrop, CdkDragEnter, CdkDragExit, CdkDragSortEvent} from '../drag-events'; import {CdkDropListGroup} from './drop-list-group'; import {DropListRef} from '../drop-list-ref'; import {DragRef} from '../drag-ref'; import {DragDrop} from '../drag-drop'; +import {DropListOrientation, DragAxis, DragDropConfig, CDK_DRAG_CONFIG} from './config'; import {Subject} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; @@ -83,7 +86,7 @@ export class CdkDropList implements AfterContentInit, OnDestroy { @Input('cdkDropListData') data: T; /** Direction in which the list is oriented. */ - @Input('cdkDropListOrientation') orientation: 'horizontal' | 'vertical' = 'vertical'; + @Input('cdkDropListOrientation') orientation: DropListOrientation; /** * Unique ID for the drop zone. Can be used as a reference @@ -92,7 +95,7 @@ export class CdkDropList implements AfterContentInit, OnDestroy { @Input() id: string = `cdk-drop-list-${_uniqueIdCounter++}`; /** Locks the position of the draggable elements inside the container along the specified axis. */ - @Input('cdkDropListLockAxis') lockAxis: 'x' | 'y'; + @Input('cdkDropListLockAxis') lockAxis: DragAxis; /** Whether starting a dragging sequence from this container is disabled. */ @Input('cdkDropListDisabled') @@ -106,11 +109,11 @@ export class CdkDropList implements AfterContentInit, OnDestroy { // the user in a disabled state, so we also need to sync it as it's being set. this._dropListRef.disabled = this._disabled = coerceBooleanProperty(value); } - private _disabled = false; + private _disabled: boolean; /** Whether sorting within this drop list is disabled. */ @Input('cdkDropListSortingDisabled') - sortingDisabled: boolean = false; + sortingDisabled: boolean; /** * Function that is used to determine whether an item @@ -121,7 +124,7 @@ export class CdkDropList implements AfterContentInit, OnDestroy { /** Whether to auto-scroll the view when the user moves their pointer close to the edges. */ @Input('cdkDropListAutoScrollDisabled') - autoScrollDisabled: boolean = false; + autoScrollDisabled: boolean; /** Emits when the user drops an item inside the container. */ @Output('cdkDropListDropped') @@ -148,9 +151,21 @@ export class CdkDropList implements AfterContentInit, OnDestroy { /** Element that the drop list is attached to. */ public element: ElementRef, dragDrop: DragDrop, private _changeDetectorRef: ChangeDetectorRef, @Optional() private _dir?: Directionality, - @Optional() @SkipSelf() private _group?: CdkDropListGroup) { + @Optional() @SkipSelf() private _group?: CdkDropListGroup, + + /** + * @deprecated _scrollDispatcher parameter to become required. + * @breaking-change 11.0.0 + */ + private _scrollDispatcher?: ScrollDispatcher, + @Optional() @Inject(CDK_DRAG_CONFIG) config?: DragDropConfig) { this._dropListRef = dragDrop.createDropList(element); this._dropListRef.data = this; + + if (config) { + this._assignDefaults(config); + } + this._dropListRef.enterPredicate = (drag: DragRef, drop: DropListRef) => { return this.enterPredicate(drag.data, drop.data); }; @@ -165,6 +180,14 @@ export class CdkDropList implements AfterContentInit, OnDestroy { } ngAfterContentInit() { + // @breaking-change 11.0.0 Remove null check for _scrollDispatcher once it's required. + if (this._scrollDispatcher) { + const scrollableParents = this._scrollDispatcher + .getAncestorScrollContainers(this.element) + .map(scrollable => scrollable.getElementRef().nativeElement); + this._dropListRef.withScrollableParents(scrollableParents); + } + this._draggables.changes .pipe(startWith(this._draggables), takeUntil(this._destroyed)) .subscribe((items: QueryList) => { @@ -332,6 +355,22 @@ export class CdkDropList implements AfterContentInit, OnDestroy { }); } + /** Assigns the default input values based on a provided config object. */ + private _assignDefaults(config: DragDropConfig) { + const { + lockAxis, draggingDisabled, sortingDisabled, listAutoScrollDisabled, listOrientation + } = config; + + this.disabled = draggingDisabled == null ? false : draggingDisabled; + this.sortingDisabled = sortingDisabled == null ? false : sortingDisabled; + this.autoScrollDisabled = listAutoScrollDisabled == null ? false : listAutoScrollDisabled; + this.orientation = listOrientation || 'vertical'; + + if (lockAxis) { + this.lockAxis = lockAxis; + } + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_sortingDisabled: BooleanInput; static ngAcceptInputType_autoScrollDisabled: BooleanInput; diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index fac3ab655dc0..97193ca1a993 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -64,6 +64,12 @@ interface DragHelperTemplate { context: T; } +/** Point on the page or within an element. */ +export interface Point { + x: number; + y: number; +} + /** * Reference to a draggable item. Used to manipulate or dispose of the item. */ @@ -87,10 +93,10 @@ export class DragRef { private _pickupPositionOnPage: Point; /** - * Reference to the element that comes after the draggable in the DOM, at the time - * it was picked up. Used for restoring its initial position when it's dropped. + * Anchor node used to save the place in the DOM where the element was + * picked up so that it can be restored at the end of the drag sequence. */ - private _nextSibling: Node | null; + private _anchor: Comment; /** * CSS `transform` applied to the element when it isn't being dragged. We need a @@ -374,9 +380,10 @@ export class DragRef { if (this.isDragging()) { // Since we move out the element to the end of the body while it's being // dragged, we have to make sure that it's removed if it gets destroyed. - removeElement(this._rootElement); + removeNode(this._rootElement); } + removeNode(this._anchor); this._destroyPreview(); this._destroyPlaceholder(); this._dragDropRegistry.removeDragItem(this); @@ -394,7 +401,7 @@ export class DragRef { this._dropContainer = undefined; this._resizeSubscription.unsubscribe(); this._boundaryElement = this._rootElement = this._placeholderTemplate = - this._previewTemplate = this._nextSibling = null!; + this._previewTemplate = this._anchor = null!; } /** Checks whether the element is currently being dragged. */ @@ -481,7 +488,7 @@ export class DragRef { /** Destroys the preview element and its ViewRef. */ private _destroyPreview() { if (this._preview) { - removeElement(this._preview); + removeNode(this._preview); } if (this._previewRef) { @@ -494,7 +501,7 @@ export class DragRef { /** Destroys the placeholder element and its ViewRef. */ private _destroyPlaceholder() { if (this._placeholder) { - removeElement(this._placeholder); + removeNode(this._placeholder); } if (this._placeholderRef) { @@ -525,6 +532,10 @@ export class DragRef { /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */ private _pointerMove = (event: MouseEvent | TouchEvent) => { + // Prevent the default action as early as possible in order to block + // native actions like dragging the selected text or images with the mouse. + event.preventDefault(); + if (!this._hasStartedDragging) { const pointerPosition = this._getPointerPositionOnPage(event); const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x); @@ -565,7 +576,6 @@ export class DragRef { const constrainedPointerPosition = this._getConstrainedPointerPosition(event); this._hasMoved = true; - event.preventDefault(); this._updatePointerDirectionDelta(constrainedPointerPosition); if (this._dropContainer) { @@ -672,19 +682,19 @@ export class DragRef { if (this._dropContainer) { const element = this._rootElement; - - // Grab the `nextSibling` before the preview and placeholder - // have been created so we don't get the preview by accident. - this._nextSibling = element.nextSibling; - + const parent = element.parentNode!; const preview = this._preview = this._createPreviewElement(); const placeholder = this._placeholder = this._createPlaceholderElement(); + const anchor = this._anchor = this._anchor || this._document.createComment(''); + + // Insert an anchor node so that we can restore the element's position in the DOM. + parent.insertBefore(anchor, element); // We move the element out at the end of the body and we make it hidden, because keeping it in // place will throw off the consumer's `:last-child` selectors. We can't remove the element // from the DOM completely, because iOS will stop firing all subsequent events in the chain. element.style.display = 'none'; - this._document.body.appendChild(element.parentNode!.replaceChild(placeholder, element)); + this._document.body.appendChild(parent.replaceChild(placeholder, element)); getPreviewInsertionPoint(this._document).appendChild(preview); this._dropContainer.start(); } @@ -767,12 +777,7 @@ export class DragRef { // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary, // while moving the existing elements in all other cases. this._rootElement.style.display = ''; - - if (this._nextSibling) { - this._nextSibling.parentNode!.insertBefore(this._rootElement, this._nextSibling); - } else { - coerceElement(this._initialContainer.element).appendChild(this._rootElement); - } + this._anchor.parentNode!.replaceChild(this._rootElement, this._anchor); this._destroyPreview(); this._destroyPlaceholder(); @@ -1183,12 +1188,6 @@ export class DragRef { } } -/** Point on the page or within an element. */ -export interface Point { - x: number; - y: number; -} - /** * Gets a 3d `transform` that can be applied to an element. * @param x Desired position of the element along the X axis. @@ -1236,12 +1235,12 @@ function clamp(value: number, min: number, max: number) { } /** - * Helper to remove an element from the DOM and to do all the necessary null checks. - * @param element Element to be removed. + * Helper to remove a node from the DOM and to do all the necessary null checks. + * @param node Node to be removed. */ -function removeElement(element: HTMLElement | null) { - if (element && element.parentNode) { - element.parentNode.removeChild(element); +function removeNode(node: Node | null) { + if (node && node.parentNode) { + node.parentNode.removeChild(node); } } diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts index b964e30ea246..e8c3be950cc3 100644 --- a/src/cdk/drag-drop/drop-list-ref.ts +++ b/src/cdk/drag-drop/drop-list-ref.ts @@ -137,11 +137,11 @@ export class DropListRef { /** Cache of the dimensions of all the items inside the container. */ private _itemPositions: CachedItemPosition[] = []; - /** Keeps track of the container's scroll position. */ - private _scrollPosition: ScrollPosition = {top: 0, left: 0}; - - /** Keeps track of the scroll position of the viewport. */ - private _viewportScrollPosition: ScrollPosition = {top: 0, left: 0}; + /** Cached positions of the scrollable parent elements. */ + private _parentPositions = new Map(); /** Cached `ClientRect` of the drop list. */ private _clientRect: ClientRect; @@ -195,6 +195,12 @@ export class DropListRef { /** Reference to the document. */ private _document: Document; + /** Elements that can be scrolled while the user is dragging. */ + private _scrollableElements: HTMLElement[]; + + /** Initial value for the element's `scroll-snap-type` style. */ + private _initialScrollSnap: string; + constructor( element: ElementRef | HTMLElement, private _dragDropRegistry: DragDropRegistry, @@ -203,6 +209,7 @@ export class DropListRef { private _viewportRuler: ViewportRuler) { this.element = coerceElement(element); this._document = _document; + this.withScrollableParents([this.element]); _dragDropRegistry.registerDropContainer(this); } @@ -210,7 +217,7 @@ export class DropListRef { dispose() { this._stopScrolling(); this._stopScrollTimers.complete(); - this._removeListeners(); + this._viewportScrollSubscription.unsubscribe(); this.beforeStarted.complete(); this.entered.complete(); this.exited.complete(); @@ -218,6 +225,7 @@ export class DropListRef { this.sorted.complete(); this._activeSiblings.clear(); this._scrollNode = null!; + this._parentPositions.clear(); this._dragDropRegistry.removeDropContainer(this); } @@ -228,13 +236,18 @@ export class DropListRef { /** Starts dragging an item. */ start(): void { - const element = coerceElement(this.element); + const styles = coerceElement(this.element).style; this.beforeStarted.next(); this._isDragging = true; + + // We need to disable scroll snapping while the user is dragging, because it breaks automatic + // scrolling. The browser seems to round the value based on the snapping points which means + // that we can't increment/decrement the scroll position. + this._initialScrollSnap = styles.msScrollSnapType || (styles as any).scrollSnapType || ''; + (styles as any).scrollSnapType = styles.msScrollSnapType = 'none'; this._cacheItems(); this._siblings.forEach(sibling => sibling._startReceiving(this)); - this._removeListeners(); - this._ngZone.runOutsideAngular(() => element.addEventListener('scroll', this._handleScroll)); + this._viewportScrollSubscription.unsubscribe(); this._listenToScrollEvents(); } @@ -367,6 +380,20 @@ export class DropListRef { return this; } + /** + * Sets which parent elements are can be scrolled while the user is dragging. + * @param elements Elements that can be scrolled. + */ + withScrollableParents(elements: HTMLElement[]): this { + const element = coerceElement(this.element); + + // We always allow the current element to be scrollable + // so we need to ensure that it's in the array. + this._scrollableElements = + elements.indexOf(element) === -1 ? [element, ...elements] : elements.slice(); + return this; + } + /** * Figures out the index of an item in the container. * @param item Item whose index should be determined. @@ -403,7 +430,7 @@ export class DropListRef { _sortItem(item: DragRef, pointerX: number, pointerY: number, pointerDelta: {x: number, y: number}): void { // Don't sort the item if sorting is disabled or it's out of range. - if (this.sortingDisabled || !this._isPointerNearDropContainer(pointerX, pointerY)) { + if (this.sortingDisabled || !isPointerNearClientRect(this._clientRect, pointerX, pointerY)) { return; } @@ -489,17 +516,23 @@ export class DropListRef { let verticalScrollDirection = AutoScrollVerticalDirection.NONE; let horizontalScrollDirection = AutoScrollHorizontalDirection.NONE; - // Check whether we should start scrolling the container. - if (this._isPointerNearDropContainer(pointerX, pointerY)) { - const element = coerceElement(this.element); + // Check whether we should start scrolling any of the parent containers. + this._parentPositions.forEach((position, element) => { + // We have special handling for the `document` below. Also this would be + // nicer with a for...of loop, but it requires changing a compiler flag. + if (element === this._document || !position.clientRect || scrollNode) { + return; + } - [verticalScrollDirection, horizontalScrollDirection] = - getElementScrollDirections(element, this._clientRect, pointerX, pointerY); + if (isPointerNearClientRect(position.clientRect, pointerX, pointerY)) { + [verticalScrollDirection, horizontalScrollDirection] = getElementScrollDirections( + element as HTMLElement, position.clientRect, pointerX, pointerY); - if (verticalScrollDirection || horizontalScrollDirection) { - scrollNode = element; + if (verticalScrollDirection || horizontalScrollDirection) { + scrollNode = element as HTMLElement; + } } - } + }); // Otherwise check if we can start scrolling the viewport. if (!verticalScrollDirection && !horizontalScrollDirection) { @@ -530,11 +563,27 @@ export class DropListRef { this._stopScrollTimers.next(); } - /** Caches the position of the drop list. */ - private _cacheOwnPosition() { - const element = coerceElement(this.element); - this._clientRect = getMutableClientRect(element); - this._scrollPosition = {top: element.scrollTop, left: element.scrollLeft}; + /** Caches the positions of the configured scrollable parents. */ + private _cacheParentPositions() { + this._parentPositions.clear(); + this._parentPositions.set(this._document, { + scrollPosition: this._viewportRuler!.getViewportScrollPosition(), + }); + this._scrollableElements.forEach(element => { + const clientRect = getMutableClientRect(element); + + // We keep the ClientRect cached in two properties, because it's referenced in a lot of + // performance-sensitive places and we want to avoid the extra lookups. The `element` is + // guaranteed to always be in the `_scrollableElements` so this should always match. + if (element === this.element) { + this._clientRect = clientRect; + } + + this._parentPositions.set(element, { + scrollPosition: {top: element.scrollTop, left: element.scrollLeft}, + clientRect + }); + }); } /** Refreshes the position cache of the items and sibling containers. */ @@ -558,6 +607,9 @@ export class DropListRef { private _reset() { this._isDragging = false; + const styles = coerceElement(this.element).style; + (styles as any).scrollSnapType = styles.msScrollSnapType = this._initialScrollSnap; + // TODO(crisbeto): may have to wait for the animations to finish. this._activeDraggables.forEach(item => item.getRootElement().style.transform = ''); this._siblings.forEach(sibling => sibling._stopReceiving(this)); @@ -566,7 +618,8 @@ export class DropListRef { this._previousSwap.drag = null; this._previousSwap.delta = 0; this._stopScrolling(); - this._removeListeners(); + this._viewportScrollSubscription.unsubscribe(); + this._parentPositions.clear(); } /** @@ -602,20 +655,6 @@ export class DropListRef { return siblingOffset; } - /** - * Checks whether the pointer coordinates are close to the drop container. - * @param pointerX Coordinates along the X axis. - * @param pointerY Coordinates along the Y axis. - */ - private _isPointerNearDropContainer(pointerX: number, pointerY: number): boolean { - const {top, right, bottom, left, width, height} = this._clientRect; - const xThreshold = width * DROP_PROXIMITY_THRESHOLD; - const yThreshold = height * DROP_PROXIMITY_THRESHOLD; - - return pointerY > top - yThreshold && pointerY < bottom + yThreshold && - pointerX > left - xThreshold && pointerX < right + xThreshold; - } - /** * Gets the offset in pixels by which the item that is being dragged should be moved. * @param currentPosition Current position of the item. @@ -676,26 +715,29 @@ export class DropListRef { private _cacheItems(): void { this._activeDraggables = this._draggables.slice(); this._cacheItemPositions(); - this._cacheOwnPosition(); + this._cacheParentPositions(); } /** * Updates the internal state of the container after a scroll event has happened. - * @param scrollPosition Object that is keeping track of the scroll position. + * @param scrolledParent Element that was scrolled. * @param newTop New top scroll position. * @param newLeft New left scroll position. - * @param extraClientRect Extra `ClientRect` object that should be updated, in addition to the - * ones of the drag items. Useful when the viewport has been scrolled and we also need to update - * the `ClientRect` of the list. */ - private _updateAfterScroll(scrollPosition: ScrollPosition, newTop: number, newLeft: number, - extraClientRect?: ClientRect) { + private _updateAfterScroll(scrolledParent: HTMLElement | Document, + newTop: number, + newLeft: number) { + const scrollPosition = this._parentPositions.get(scrolledParent)!.scrollPosition; const topDifference = scrollPosition.top - newTop; const leftDifference = scrollPosition.left - newLeft; - if (extraClientRect) { - adjustClientRect(extraClientRect, topDifference, leftDifference); - } + // Go through and update the cached positions of the scroll + // parents that are inside the element that was scrolled. + this._parentPositions.forEach((position, node) => { + if (position.clientRect && scrolledParent !== node && scrolledParent.contains(node)) { + adjustClientRect(position.clientRect, topDifference, leftDifference); + } + }); // Since we know the amount that the user has scrolled we can shift all of the client rectangles // ourselves. This is cheaper than re-measuring everything and we can avoid inconsistent @@ -718,22 +760,6 @@ export class DropListRef { scrollPosition.left = newLeft; } - /** Handles the container being scrolled. Has to be an arrow function to preserve the context. */ - private _handleScroll = () => { - if (!this.isDragging()) { - return; - } - - const element = coerceElement(this.element); - this._updateAfterScroll(this._scrollPosition, element.scrollTop, element.scrollLeft); - } - - /** Removes the event listeners associated with this drop list. */ - private _removeListeners() { - coerceElement(this.element).removeEventListener('scroll', this._handleScroll); - this._viewportScrollSubscription.unsubscribe(); - } - /** Starts the interval that'll auto-scroll the element. */ private _startScrollInterval = () => { this._stopScrolling(); @@ -816,7 +842,7 @@ export class DropListRef { if (!activeSiblings.has(sibling)) { activeSiblings.add(sibling); - this._cacheOwnPosition(); + this._cacheParentPositions(); this._listenToScrollEvents(); } } @@ -835,14 +861,28 @@ export class DropListRef { * Used for updating the internal state of the list. */ private _listenToScrollEvents() { - this._viewportScrollPosition = this._viewportRuler!.getViewportScrollPosition(); - this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe(() => { + this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe(event => { if (this.isDragging()) { - const newPosition = this._viewportRuler!.getViewportScrollPosition(); - this._updateAfterScroll(this._viewportScrollPosition, newPosition.top, newPosition.left, - this._clientRect); + const target = event.target as HTMLElement | Document; + const position = this._parentPositions.get(target); + + if (position) { + let newTop: number; + let newLeft: number; + + if (target === this._document) { + const scrollPosition = this._viewportRuler!.getViewportScrollPosition(); + newTop = scrollPosition.top; + newLeft = scrollPosition.left; + } else { + newTop = (target as HTMLElement).scrollTop; + newLeft = (target as HTMLElement).scrollLeft; + } + + this._updateAfterScroll(target, newTop, newLeft); + } } else if (this.isReceiving()) { - this._cacheOwnPosition(); + this._cacheParentPositions(); } }); } @@ -877,6 +917,20 @@ function adjustClientRect(clientRect: ClientRect, top: number, left: number) { clientRect.right = clientRect.left + clientRect.width; } +/** + * Checks whether the pointer coordinates are close to a ClientRect. + * @param rect ClientRect to check against. + * @param pointerX Coordinates along the X axis. + * @param pointerY Coordinates along the Y axis. + */ +function isPointerNearClientRect(rect: ClientRect, pointerX: number, pointerY: number): boolean { + const {top, right, bottom, left, width, height} = rect; + const xThreshold = width * DROP_PROXIMITY_THRESHOLD; + const yThreshold = height * DROP_PROXIMITY_THRESHOLD; + + return pointerY > top - yThreshold && pointerY < bottom + yThreshold && + pointerX > left - xThreshold && pointerX < right + xThreshold; +} /** * Finds the index of an item that matches a predicate function. Used as an equivalent diff --git a/src/cdk/drag-drop/public-api.ts b/src/cdk/drag-drop/public-api.ts index 18284d498230..c5864b22f418 100644 --- a/src/cdk/drag-drop/public-api.ts +++ b/src/cdk/drag-drop/public-api.ts @@ -16,6 +16,7 @@ export * from './drag-drop-module'; export * from './drag-drop-registry'; export {CdkDropList} from './directives/drop-list'; +export * from './directives/config'; export * from './directives/drop-list-group'; export * from './directives/drag'; export * from './directives/drag-handle'; diff --git a/src/cdk/overlay/fullscreen-overlay-container.spec.ts b/src/cdk/overlay/fullscreen-overlay-container.spec.ts index 4e6279f2a275..21aa417e1305 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.spec.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.spec.ts @@ -27,28 +27,28 @@ describe('FullscreenOverlayContainer', () => { body: document.body, fullscreenElement: document.createElement('div'), fullscreenEnabled: true, - addEventListener: function(eventName: string, listener: Function) { + addEventListener: function(eventName: string, listener: EventListener) { if (eventName === 'fullscreenchange') { fullscreenListeners.add(listener); } else { - document.addEventListener.apply(document, arguments); + document.addEventListener(eventName, listener); } }, - removeEventListener: function(eventName: string, listener: Function) { + removeEventListener: function(eventName: string, listener: EventListener) { if (eventName === 'fullscreenchange') { fullscreenListeners.delete(listener); } else { - document.addEventListener.apply(document, arguments); + document.addEventListener(eventName, listener); } }, - querySelectorAll: function() { - return document.querySelectorAll.apply(document, arguments); + querySelectorAll: function(...args: [string]) { + return document.querySelectorAll(...args); }, - createElement: function() { - return document.createElement.apply(document, arguments); + createElement: function(...args: [string, (ElementCreationOptions | undefined)?]) { + return document.createElement(...args); }, - getElementsByClassName: function() { - return document.getElementsByClassName.apply(document, arguments); + getElementsByClassName: function(...args: [string]) { + return document.getElementsByClassName(...args); } }; diff --git a/src/cdk/overlay/fullscreen-overlay-container.ts b/src/cdk/overlay/fullscreen-overlay-container.ts index 0f0d8a541f22..55b33c8fd723 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.ts @@ -9,6 +9,7 @@ import {Injectable, Inject, OnDestroy} from '@angular/core'; import {OverlayContainer} from './overlay-container'; import {DOCUMENT} from '@angular/common'; +import {Platform} from '@angular/cdk/platform'; /** @@ -23,8 +24,14 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe private _fullScreenEventName: string | undefined; private _fullScreenListener: () => void; - constructor(@Inject(DOCUMENT) _document: any) { - super(_document); + constructor( + @Inject(DOCUMENT) _document: any, + /** + * @deprecated `platform` parameter to become required. + * @breaking-change 10.0.0 + */ + platform?: Platform) { + super(_document, platform); } ngOnDestroy() { diff --git a/src/cdk/overlay/overlay-container.spec.ts b/src/cdk/overlay/overlay-container.spec.ts index 226f71bebdac..eab81cbd01ca 100644 --- a/src/cdk/overlay/overlay-container.spec.ts +++ b/src/cdk/overlay/overlay-container.spec.ts @@ -53,9 +53,10 @@ describe('OverlayContainer', () => { .toBe(false, 'Expected the overlay container not to have class "commander-shepard"'); }); - it('should ensure that there is only one overlay container on the page', () => { + it('should remove overlay containers from the server when on the browser', () => { const extraContainer = document.createElement('div'); extraContainer.classList.add('cdk-overlay-container'); + extraContainer.setAttribute('platform', 'server'); document.body.appendChild(extraContainer); overlayContainer.getContainerElement(); @@ -65,6 +66,33 @@ describe('OverlayContainer', () => { extraContainer.parentNode.removeChild(extraContainer); } }); + + it('should remove overlay containers from other unit tests', () => { + const extraContainer = document.createElement('div'); + extraContainer.classList.add('cdk-overlay-container'); + extraContainer.setAttribute('platform', 'test'); + document.body.appendChild(extraContainer); + + overlayContainer.getContainerElement(); + expect(document.querySelectorAll('.cdk-overlay-container').length).toBe(1); + + if (extraContainer.parentNode) { + extraContainer.parentNode.removeChild(extraContainer); + } + }); + + it('should not remove extra containers that were created on the browser', () => { + const extraContainer = document.createElement('div'); + extraContainer.classList.add('cdk-overlay-container'); + document.body.appendChild(extraContainer); + + overlayContainer.getContainerElement(); + + expect(document.querySelectorAll('.cdk-overlay-container').length).toBe(2); + + extraContainer.parentNode!.removeChild(extraContainer); + }); + }); /** Test-bed component that contains a TempatePortal and an ElementRef. */ diff --git a/src/cdk/overlay/overlay-container.ts b/src/cdk/overlay/overlay-container.ts index 45afbc69fd74..2e078788f146 100644 --- a/src/cdk/overlay/overlay-container.ts +++ b/src/cdk/overlay/overlay-container.ts @@ -15,7 +15,14 @@ import { Optional, SkipSelf, } from '@angular/core'; +import {Platform} from '@angular/cdk/platform'; +/** + * Whether we're in a testing environment. + * TODO(crisbeto): remove this once we have an overlay testing module. + */ +const isTestEnvironment: boolean = typeof window !== 'undefined' && !!window && + !!((window as any).__karma__ || (window as any).jasmine); /** Container inside which all overlays will render. */ @Injectable({providedIn: 'root'}) @@ -23,13 +30,21 @@ export class OverlayContainer implements OnDestroy { protected _containerElement: HTMLElement; protected _document: Document; - constructor(@Inject(DOCUMENT) document: any) { + constructor( + @Inject(DOCUMENT) document: any, + /** + * @deprecated `platform` parameter to become required. + * @breaking-change 10.0.0 + */ + protected _platform?: Platform) { this._document = document; } ngOnDestroy() { - if (this._containerElement && this._containerElement.parentNode) { - this._containerElement.parentNode.removeChild(this._containerElement); + const container = this._containerElement; + + if (container && container.parentNode) { + container.parentNode.removeChild(container); } } @@ -52,16 +67,40 @@ export class OverlayContainer implements OnDestroy { * with the 'cdk-overlay-container' class on the document body. */ protected _createContainer(): void { + // @breaking-change 10.0.0 Remove null check for `_platform`. + const isBrowser = this._platform ? this._platform.isBrowser : typeof window !== 'undefined'; const containerClass = 'cdk-overlay-container'; - const previousContainers = this._document.getElementsByClassName(containerClass); - // Remove any old containers. This can happen when transitioning from the server to the client. - for (let i = 0; i < previousContainers.length; i++) { - previousContainers[i].parentNode!.removeChild(previousContainers[i]); + if (isBrowser || isTestEnvironment) { + const oppositePlatformContainers = + this._document.querySelectorAll(`.${containerClass}[platform="server"], ` + + `.${containerClass}[platform="test"]`); + + // Remove any old containers from the opposite platform. + // This can happen when transitioning from the server to the client. + for (let i = 0; i < oppositePlatformContainers.length; i++) { + oppositePlatformContainers[i].parentNode!.removeChild(oppositePlatformContainers[i]); + } } const container = this._document.createElement('div'); container.classList.add(containerClass); + + // A long time ago we kept adding new overlay containers whenever a new app was instantiated, + // but at some point we added logic which clears the duplicate ones in order to avoid leaks. + // The new logic was a little too aggressive since it was breaking some legitimate use cases. + // To mitigate the problem we made it so that only containers from a different platform are + // cleared, but the side-effect was that people started depending on the overly-aggressive + // logic to clean up their tests for them. Until we can introduce an overlay-specific testing + // module which does the cleanup, we try to detect that we're in a test environment and we + // always clear the container. See #17006. + // TODO(crisbeto): remove the test environment check once we have an overlay testing module. + if (isTestEnvironment) { + container.setAttribute('platform', 'test'); + } else if (!isBrowser) { + container.setAttribute('platform', 'server'); + } + this._document.body.appendChild(container); this._containerElement = container; } diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index d28b619fd637..d7065bd7f39a 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -114,15 +114,15 @@ export class OverlayRef implements PortalOutlet, OverlayReference { attach(portal: Portal): any { let attachResult = this._portalOutlet.attach(portal); - if (this._positionStrategy) { - this._positionStrategy.attach(this); - } - // Update the pane element with the given configuration. if (!this._host.parentElement && this._previousHostParent) { this._previousHostParent.appendChild(this._host); } + if (this._positionStrategy) { + this._positionStrategy.attach(this); + } + this._updateStackingOrder(); this._updateElementSize(); this._updateElementDirection(); diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index 7e9e3598fa99..f262dc151ff0 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -444,6 +444,29 @@ describe('Overlay', () => { expect(overlayContainerElement.querySelectorAll('.fake-positioned').length).toBe(1); })); + it('should have the overlay in the DOM in position strategy when reattaching', fakeAsync(() => { + let overlayPresentInDom = false; + + config.positionStrategy = { + attach: (ref: OverlayRef) => overlayPresentInDom = !!ref.hostElement.parentElement, + apply: () => {}, + dispose: () => {} + }; + + const overlayRef = overlay.create(config); + + overlayRef.attach(componentPortal); + expect(overlayPresentInDom).toBeTruthy('Expected host element to be attached to the DOM.'); + + overlayRef.detach(); + zone.simulateZoneExit(); + tick(); + + overlayRef.attach(componentPortal); + + expect(overlayPresentInDom).toBeTruthy('Expected host element to be attached to the DOM.'); + })); + it('should not apply the position if it detaches before the zone stabilizes', fakeAsync(() => { config.positionStrategy = new FakePositionStrategy(); diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 8f748de9a613..2aa4206d7c7c 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -921,7 +921,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { scrollPosition: ViewportScrollPosition) { // Reset any existing styles. This is necessary in case the // preferred position has changed since the last `apply`. - let styles = {top: null, bottom: null} as CSSStyleDeclaration; + let styles = {top: '', bottom: ''} as CSSStyleDeclaration; let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position); if (this._isPushed) { @@ -957,7 +957,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { scrollPosition: ViewportScrollPosition) { // Reset any existing styles. This is necessary in case the preferred position has // changed since the last `apply`. - let styles = {left: null, right: null} as CSSStyleDeclaration; + let styles = {left: '', right: ''} as CSSStyleDeclaration; let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position); if (this._isPushed) { @@ -1189,12 +1189,13 @@ export interface ConnectedPosition { } /** Shallow-extends a stylesheet object with another stylesheet object. */ -function extendStyles(dest: CSSStyleDeclaration, source: CSSStyleDeclaration): CSSStyleDeclaration { +function extendStyles(destination: CSSStyleDeclaration, + source: CSSStyleDeclaration): CSSStyleDeclaration { for (let key in source) { if (source.hasOwnProperty(key)) { - dest[key] = source[key]; + destination[key] = source[key]; } } - return dest; + return destination; } diff --git a/src/cdk/overlay/position/global-position-strategy.spec.ts b/src/cdk/overlay/position/global-position-strategy.spec.ts index 3dc67de133ef..5e35450bebd1 100644 --- a/src/cdk/overlay/position/global-position-strategy.spec.ts +++ b/src/cdk/overlay/position/global-position-strategy.spec.ts @@ -195,7 +195,7 @@ describe('GlobalPositonStrategy', () => { attachOverlay({ positionStrategy: overlay.position() .global() - .centerHorizontally() + .centerHorizontally('10px') .width('100%') }); @@ -206,11 +206,45 @@ describe('GlobalPositonStrategy', () => { expect(parentStyle.justifyContent).toBe('flex-start'); }); + it('should reset the horizontal position and offset when the width is 100% and the ' + + 'maxWidth is 100%', () => { + attachOverlay({ + maxWidth: '100%', + positionStrategy: overlay.position() + .global() + .centerHorizontally('10px') + .width('100%') + }); + + const elementStyle = overlayRef.overlayElement.style; + const parentStyle = (overlayRef.overlayElement.parentNode as HTMLElement).style; + + expect(elementStyle.marginLeft).toBe('0px'); + expect(parentStyle.justifyContent).toBe('flex-start'); + }); + + it('should not reset the horizontal position and offset when the width is 100% and' + + 'there is a defined maxWidth', () => { + attachOverlay({ + maxWidth: '500px', + positionStrategy: overlay.position() + .global() + .centerHorizontally('10px') + .width('100%') + }); + + const elementStyle = overlayRef.overlayElement.style; + const parentStyle = (overlayRef.overlayElement.parentNode as HTMLElement).style; + + expect(elementStyle.marginLeft).toBe('10px'); + expect(parentStyle.justifyContent).toBe('center'); + }); + it('should reset the vertical position and offset when the height is 100%', () => { attachOverlay({ positionStrategy: overlay.position() .global() - .centerVertically() + .centerVertically('10px') .height('100%') }); @@ -221,6 +255,40 @@ describe('GlobalPositonStrategy', () => { expect(parentStyle.alignItems).toBe('flex-start'); }); + it('should reset the vertical position and offset when the height is 100% and the ' + + 'maxHeight is 100%', () => { + attachOverlay({ + maxHeight: '100%', + positionStrategy: overlay.position() + .global() + .centerVertically('10px') + .height('100%') + }); + + const elementStyle = overlayRef.overlayElement.style; + const parentStyle = (overlayRef.overlayElement.parentNode as HTMLElement).style; + + expect(elementStyle.marginTop).toBe('0px'); + expect(parentStyle.alignItems).toBe('flex-start'); + }); + + it('should not reset the vertical position and offset when the height is 100% and ' + + 'there is a defined maxHeight', () => { + attachOverlay({ + maxHeight: '500px', + positionStrategy: overlay.position() + .global() + .centerVertically('10px') + .height('100%') + }); + + const elementStyle = overlayRef.overlayElement.style; + const parentStyle = (overlayRef.overlayElement.parentNode as HTMLElement).style; + + expect(elementStyle.marginTop).toBe('10px'); + expect(parentStyle.alignItems).toBe('center'); + }); + it('should not throw when attempting to apply after the overlay has been disposed', () => { const positionStrategy = overlay.position().global(); diff --git a/src/cdk/overlay/position/global-position-strategy.ts b/src/cdk/overlay/position/global-position-strategy.ts index 610515960c82..6b24e3a126f0 100644 --- a/src/cdk/overlay/position/global-position-strategy.ts +++ b/src/cdk/overlay/position/global-position-strategy.ts @@ -164,14 +164,19 @@ export class GlobalPositionStrategy implements PositionStrategy { const styles = this._overlayRef.overlayElement.style; const parentStyles = this._overlayRef.hostElement.style; const config = this._overlayRef.getConfig(); + const {width, height, maxWidth, maxHeight} = config; + const shouldBeFlushHorizontally = (width === '100%' || width === '100vw') && + (!maxWidth || maxWidth === '100%' || maxWidth === '100vw'); + const shouldBeFlushVertically = (height === '100%' || height === '100vh') && + (!maxHeight || maxHeight === '100%' || maxHeight === '100vh'); styles.position = this._cssPosition; - styles.marginLeft = config.width === '100%' ? '0' : this._leftOffset; - styles.marginTop = config.height === '100%' ? '0' : this._topOffset; + styles.marginLeft = shouldBeFlushHorizontally ? '0' : this._leftOffset; + styles.marginTop = shouldBeFlushVertically ? '0' : this._topOffset; styles.marginBottom = this._bottomOffset; styles.marginRight = this._rightOffset; - if (config.width === '100%') { + if (shouldBeFlushHorizontally) { parentStyles.justifyContent = 'flex-start'; } else if (this._justifyContent === 'center') { parentStyles.justifyContent = 'center'; @@ -189,7 +194,7 @@ export class GlobalPositionStrategy implements PositionStrategy { parentStyles.justifyContent = this._justifyContent; } - parentStyles.alignItems = config.height === '100%' ? 'flex-start' : this._alignItems; + parentStyles.alignItems = shouldBeFlushVertically ? 'flex-start' : this._alignItems; } /** diff --git a/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts b/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts index 7ac5f6596f2f..6ee4685149d8 100644 --- a/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts +++ b/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts @@ -105,7 +105,7 @@ describe('RepositionScrollStrategy', () => { right: 100, width: 100, height: 100 - }); + } as DOMRect); scrolledSubject.next(); expect(overlayRef.detach).toHaveBeenCalledTimes(1); diff --git a/src/cdk/portal/portal.spec.ts b/src/cdk/portal/portal.spec.ts index c9a1db266945..58ef243d919f 100644 --- a/src/cdk/portal/portal.spec.ts +++ b/src/cdk/portal/portal.spec.ts @@ -1,23 +1,24 @@ -import {inject, ComponentFixture, TestBed} from '@angular/core/testing'; +import {CommonModule} from '@angular/common'; import { - NgModule, + ApplicationRef, Component, - ViewChild, - ViewChildren, - QueryList, - ViewContainerRef, ComponentFactoryResolver, - Optional, - Injector, - ApplicationRef, - TemplateRef, ComponentRef, ElementRef, + Injector, + NgModule, + Optional, + QueryList, + TemplateRef, + Type, + ViewChild, + ViewChildren, + ViewContainerRef, } from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {CdkPortal, CdkPortalOutlet, PortalModule} from './portal-directives'; -import {Portal, ComponentPortal, TemplatePortal, DomPortal} from './portal'; +import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {DomPortalOutlet} from './dom-portal-outlet'; +import {ComponentPortal, DomPortal, Portal, TemplatePortal} from './portal'; +import {CdkPortal, CdkPortalOutlet, PortalModule} from './portal-directives'; describe('Portals', () => { @@ -377,10 +378,9 @@ describe('Portals', () => { it('should use the `ComponentFactoryResolver` from the portal, if available', () => { const spy = jasmine.createSpy('resolveComponentFactorySpy'); const portal = new ComponentPortal(PizzaMsg, undefined, undefined, { - resolveComponentFactory: (...args: any[]) => { + resolveComponentFactory: (...args: [Type]) => { spy(); - return componentFactoryResolver.resolveComponentFactory - .apply(componentFactoryResolver, args); + return componentFactoryResolver.resolveComponentFactory(...args); } }); @@ -560,10 +560,9 @@ describe('Portals', () => { it('should use the `ComponentFactoryResolver` from the portal, if available', () => { const spy = jasmine.createSpy('resolveComponentFactorySpy'); const portal = new ComponentPortal(PizzaMsg, undefined, undefined, { - resolveComponentFactory: (...args: any[]) => { + resolveComponentFactory: (...args: [Type]) => { spy(); - return componentFactoryResolver.resolveComponentFactory - .apply(componentFactoryResolver, args); + return componentFactoryResolver.resolveComponentFactory(...args); } }); diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel index 6b156a833f95..1ea6847b13fe 100644 --- a/src/cdk/schematics/BUILD.bazel +++ b/src/cdk/schematics/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "npm_package") +load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") load("//:packages.bzl", "VERSION_PLACEHOLDER_REPLACEMENTS") load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") @@ -40,10 +40,10 @@ ts_library( ) # This package is intended to be combined into the main @angular/cdk package as a dep. -npm_package( +pkg_npm( name = "npm_package", srcs = [":schematics_assets"], - replacements = VERSION_PLACEHOLDER_REPLACEMENTS, + substitutions = VERSION_PLACEHOLDER_REPLACEMENTS, deps = [":schematics"], ) diff --git a/src/cdk/schematics/index.ts b/src/cdk/schematics/index.ts index 1bdf47dadb19..3d6f1301e6bf 100644 --- a/src/cdk/schematics/index.ts +++ b/src/cdk/schematics/index.ts @@ -10,6 +10,10 @@ export * from './utils'; export * from './ng-update/public-api'; export * from './update-tool/public-api'; +// Re-exported so that Angular Material schematic code can consume the +// vendored "@schematics/angular" AST utils. +export * from './utils/vendored-ast-utils'; + // Re-export parse5 from the CDK. Material schematics code cannot simply import // "parse5" because it could result in a different version. As long as we import // it from within the CDK, it will always be the correct version that is specified diff --git a/src/cdk/schematics/ng-add/index.spec.ts b/src/cdk/schematics/ng-add/index.spec.ts index 7b426389ce85..30e837f97a7b 100644 --- a/src/cdk/schematics/ng-add/index.spec.ts +++ b/src/cdk/schematics/ng-add/index.spec.ts @@ -16,10 +16,12 @@ describe('CDK ng-add', () => { const packageJson = JSON.parse(getFileContent(tree, '/package.json')); const dependencies = packageJson.dependencies; - expect(dependencies['@angular/cdk']).toBeDefined(); + expect(dependencies['@angular/cdk']).toBe('~0.0.0-PLACEHOLDER'); expect(Object.keys(dependencies)) .toEqual( Object.keys(dependencies).sort(), 'Expected the modified "dependencies" to be sorted alphabetically.'); + expect(runner.tasks.some(task => task.name === 'node-package')).toBe(true, + 'Expected the package manager to be scheduled in order to update lock files.'); }); }); diff --git a/src/cdk/schematics/ng-add/index.ts b/src/cdk/schematics/ng-add/index.ts index 5abdfaed9fd3..3165c6837ca7 100644 --- a/src/cdk/schematics/ng-add/index.ts +++ b/src/cdk/schematics/ng-add/index.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Rule, Tree} from '@angular-devkit/schematics'; +import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; +import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; import {addPackageToPackageJson} from './package-config'; -/** Name of the Angular CDK version that is shipped together with the schematics. */ -export const cdkVersion = loadPackageVersionGracefully('@angular/cdk'); - /** * Schematic factory entry-point for the `ng-add` schematic. The ng-add schematic will be * automatically executed if developers run `ng add @angular/cdk`. @@ -21,18 +19,14 @@ export const cdkVersion = loadPackageVersionGracefully('@angular/cdk'); * this ensures that there will be no error that says that the CDK does not support `ng add`. */ export default function(): Rule { - return (host: Tree) => { - // In order to align the CDK version with the other Angular dependencies, we use tilde - // instead of caret. This is default for Angular dependencies in new CLI projects. - addPackageToPackageJson(host, '@angular/cdk', `~${cdkVersion}`); - }; -} + return (host: Tree, context: SchematicContext) => { + // In order to align the CDK version with other Angular dependencies that are setup + // by "@schematics/angular", we use tilde instead of caret. This is default for Angular + // dependencies in new CLI projects. + addPackageToPackageJson(host, '@angular/cdk', `~0.0.0-PLACEHOLDER`); -/** Loads the full version from the given Angular package gracefully. */ -function loadPackageVersionGracefully(packageName: string): string|null { - try { - return require(`${packageName}/package.json`).version; - } catch { - return null; - } + // Add a task to run the package manager. This is necessary because we updated the + // workspace "package.json" file and we want lock files to reflect the new version range. + context.addTask(new NodePackageInstallTask()); + }; } diff --git a/src/cdk/schematics/utils/ast.ts b/src/cdk/schematics/utils/ast.ts index 5b43a9732ddc..8660e1ea3e46 100644 --- a/src/cdk/schematics/utils/ast.ts +++ b/src/cdk/schematics/utils/ast.ts @@ -9,17 +9,15 @@ import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; import {SchematicsException, Tree} from '@angular-devkit/schematics'; import {Schema as ComponentOptions} from '@schematics/angular/component/schema'; -import {addImportToModule} from '@schematics/angular/utility/ast-utils'; import {InsertChange} from '@schematics/angular/utility/change'; import {getWorkspace} from '@schematics/angular/utility/config'; import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module'; -import {getAppModulePath} from '@schematics/angular/utility/ng-ast-utils'; +import * as ts from 'typescript'; import {getProjectMainFile} from './project-main-file'; -import {ts, typescript} from './version-agnostic-typescript'; - +import {addImportToModule, getAppModulePath} from './vendored-ast-utils'; /** Reads file given path and returns TypeScript source file. */ -export function getSourceFile(host: Tree, path: string): typescript.SourceFile { +export function parseSourceFile(host: Tree, path: string): ts.SourceFile { const buffer = host.read(path); if (!buffer) { throw new SchematicsException(`Could not find file for path: ${path}`); @@ -44,7 +42,7 @@ export function addModuleImportToRootModule(host: Tree, moduleName: string, src: export function addModuleImportToModule(host: Tree, modulePath: string, moduleName: string, src: string) { - const moduleSource = getSourceFile(host, modulePath); + const moduleSource = parseSourceFile(host, modulePath); if (!moduleSource) { throw new SchematicsException(`Module not found: ${modulePath}`); diff --git a/src/cdk/schematics/utils/build-component.ts b/src/cdk/schematics/utils/build-component.ts index 24290ee4e001..48361fc22fd7 100644 --- a/src/cdk/schematics/utils/build-component.ts +++ b/src/cdk/schematics/utils/build-component.ts @@ -23,11 +23,6 @@ import { } from '@angular-devkit/schematics'; import {FileSystemSchematicContext} from '@angular-devkit/schematics/tools'; import {Schema as ComponentOptions, Style} from '@schematics/angular/component/schema'; -import { - addDeclarationToModule, - addEntryComponentToModule, - addExportToModule, -} from '@schematics/angular/utility/ast-utils'; import {InsertChange} from '@schematics/angular/utility/change'; import {getWorkspace} from '@schematics/angular/utility/config'; import {buildRelativePath, findModuleFromOptions} from '@schematics/angular/utility/find-module'; @@ -36,9 +31,14 @@ import {buildDefaultPath} from '@schematics/angular/utility/project'; import {validateHtmlSelector, validateName} from '@schematics/angular/utility/validation'; import {readFileSync, statSync} from 'fs'; import {dirname, join, resolve} from 'path'; +import * as ts from 'typescript'; +import { + addDeclarationToModule, + addEntryComponentToModule, + addExportToModule, +} from '../utils/vendored-ast-utils'; import {getProjectFromWorkspace} from './get-project'; import {getDefaultComponentOptions} from './schematic-options'; -import {ts} from './version-agnostic-typescript'; /** * List of style extensions which are CSS compatible. All supported CLI style extensions can be diff --git a/src/cdk/schematics/utils/index.ts b/src/cdk/schematics/utils/index.ts index c4bd126e1d0f..9794905f1684 100644 --- a/src/cdk/schematics/utils/index.ts +++ b/src/cdk/schematics/utils/index.ts @@ -17,4 +17,3 @@ export * from './project-main-file'; export * from './project-style-file'; export * from './project-targets'; export * from './schematic-options'; -export * from './version-agnostic-typescript'; diff --git a/src/cdk/schematics/utils/vendored-ast-utils/index.ts b/src/cdk/schematics/utils/vendored-ast-utils/index.ts new file mode 100644 index 000000000000..40798fac1960 --- /dev/null +++ b/src/cdk/schematics/utils/vendored-ast-utils/index.ts @@ -0,0 +1,575 @@ +/** + * @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 + */ + +// tslint:disable + +/* + * Note: This file contains vendored TypeScript AST utils from "@schematics/angular". + * Since there is no canonical place for common utils, and we don't want to use the AST + * utils directly from "@schematics/angular" to avoid TypeScript version mismatches, we + * copy the needed AST utils until there is a general place for such utility functions. + * + * Taken from: + * (1) https://github.com/angular/angular-cli/blob/30df1470a0f18989db336d50b55a79021ab64c85/packages/schematics/angular/utility/ng-ast-utils.ts + * (2) https://github.com/angular/angular-cli/blob/30df1470a0f18989db336d50b55a79021ab64c85/packages/schematics/angular/utility/ast-utils.ts + */ + +import {normalize} from '@angular-devkit/core'; +import {SchematicsException, Tree} from '@angular-devkit/schematics'; +import {Change, InsertChange, NoopChange} from '@schematics/angular/utility/change'; +import {dirname} from 'path'; + +import * as ts from 'typescript'; + +/** + * Add Import `import { symbolName } from fileName` if the import doesn't exit + * already. Assumes fileToEdit can be resolved and accessed. + * @param fileToEdit (file we want to add import to) + * @param symbolName (item to import) + * @param fileName (path to the file) + * @param isDefault (if true, import follows style for importing default exports) + * @return Change + */ +export function insertImport(source: ts.SourceFile, fileToEdit: string, symbolName: string, + fileName: string, isDefault = false): Change { + const rootNode = source; + const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration); + + // get nodes that map to import statements from the file fileName + const relevantImports = allImports.filter(node => { + // StringLiteral of the ImportDeclaration is the import file (fileName in this case). + const importFiles = node.getChildren() + .filter(child => child.kind === ts.SyntaxKind.StringLiteral) + .map(n => (n as ts.StringLiteral).text); + + return importFiles.filter(file => file === fileName).length === 1; + }); + + if (relevantImports.length > 0) { + let importsAsterisk = false; + // imports from import file + const imports: ts.Node[] = []; + relevantImports.forEach(n => { + Array.prototype.push.apply(imports, findNodes(n, ts.SyntaxKind.Identifier)); + if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) { + importsAsterisk = true; + } + }); + + // if imports * from fileName, don't add symbolName + if (importsAsterisk) { + return new NoopChange(); + } + + const importTextNodes = imports.filter(n => (n as ts.Identifier).text === symbolName); + + // insert import if it's not there + if (importTextNodes.length === 0) { + const fallbackPos = + findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].getStart() || + findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart(); + + return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos); + } + + return new NoopChange(); + } + + // no such import declaration exists + const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral) + .filter((n: ts.StringLiteral) => n.text === 'use strict'); + let fallbackPos = 0; + if (useStrict.length > 0) { + fallbackPos = useStrict[0].end; + } + const open = isDefault ? '' : '{ '; + const close = isDefault ? '' : ' }'; + // if there are no imports or 'use strict' statement, insert import at beginning of file + const insertAtBeginning = allImports.length === 0 && useStrict.length === 0; + const separator = insertAtBeginning ? '' : ';\n'; + const toInsert = `${separator}import ${open}${symbolName}${close}` + + ` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`; + + return insertAfterLastOccurrence( + allImports, + toInsert, + fileToEdit, + fallbackPos, + ts.SyntaxKind.StringLiteral, + ); +} + + +/** + * Find all nodes from the AST in the subtree of node of SyntaxKind kind. + * @param node + * @param kind + * @param max The maximum number of items to return. + * @param recursive Continue looking for nodes of kind recursive until end + * the last child even when node of kind has been found. + * @return all nodes of kind, or [] if none is found + */ +export function findNodes(node: ts.Node, kind: ts.SyntaxKind, max = Infinity, recursive = false): ts.Node[] { + if (!node || max == 0) { + return []; + } + + const arr: ts.Node[] = []; + if (node.kind === kind) { + arr.push(node); + max--; + } + if (max > 0 && (recursive || node.kind !== kind)) { + for (const child of node.getChildren()) { + findNodes(child, kind, max).forEach(node => { + if (max > 0) { + arr.push(node); + } + max--; + }); + + if (max <= 0) { + break; + } + } + } + + return arr; +} + + +/** + * Get all the nodes from a source. + * @param sourceFile The source file object. + * @returns {Observable} An observable of all the nodes in the source. + */ +export function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] { + const nodes: ts.Node[] = [sourceFile]; + const result: ts.Node[] = []; + + while (nodes.length > 0) { + const node = nodes.shift(); + + if (node) { + result.push(node); + if (node.getChildCount(sourceFile) >= 0) { + nodes.unshift(...node.getChildren()); + } + } + } + + return result; +} + +export function findNode(node: ts.Node, kind: ts.SyntaxKind, text: string): ts.Node | null { + if (node.kind === kind && node.getText() === text) { + // throw new Error(node.getText()); + return node; + } + + let foundNode: ts.Node | null = null; + ts.forEachChild(node, childNode => { + foundNode = foundNode || findNode(childNode, kind, text); + }); + + return foundNode; +} + + +/** + * Helper for sorting nodes. + * @return function to sort nodes in increasing order of position in sourceFile + */ +function nodesByPosition(first: ts.Node, second: ts.Node): number { + return first.getStart() - second.getStart(); +} + + +/** + * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]` + * or after the last of occurence of `syntaxKind` if the last occurence is a sub child + * of ts.SyntaxKind[nodes[i].kind] and save the changes in file. + * + * @param nodes insert after the last occurence of nodes + * @param toInsert string to insert + * @param file file to insert changes into + * @param fallbackPos position to insert if toInsert happens to be the first occurence + * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after + * @return Change instance + * @throw Error if toInsert is first occurence but fall back is not set + */ +export function insertAfterLastOccurrence(nodes: ts.Node[], + toInsert: string, + file: string, + fallbackPos: number, + syntaxKind?: ts.SyntaxKind): Change { + let lastItem: ts.Node | undefined; + for (const node of nodes) { + if (!lastItem || lastItem.getStart() < node.getStart()) { + lastItem = node; + } + } + if (syntaxKind && lastItem) { + lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop(); + } + if (!lastItem && fallbackPos == undefined) { + throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`); + } + const lastItemPosition: number = lastItem ? lastItem.getEnd() : fallbackPos; + + return new InsertChange(file, lastItemPosition, toInsert); +} + +function _angularImportsFromNode(node: ts.ImportDeclaration, + _sourceFile: ts.SourceFile): {[name: string]: string} { + const ms = node.moduleSpecifier; + let modulePath: string; + switch (ms.kind) { + case ts.SyntaxKind.StringLiteral: + modulePath = (ms as ts.StringLiteral).text; + break; + default: + return {}; + } + + if (!modulePath.startsWith('@angular/')) { + return {}; + } + + if (node.importClause) { + if (node.importClause.name) { + // This is of the form `import Name from 'path'`. Ignore. + return {}; + } else if (node.importClause.namedBindings) { + const nb = node.importClause.namedBindings; + if (nb.kind == ts.SyntaxKind.NamespaceImport) { + // This is of the form `import * as name from 'path'`. Return `name.`. + return { + [(nb as ts.NamespaceImport).name.text + '.']: modulePath, + }; + } else { + // This is of the form `import {a,b,c} from 'path'` + const namedImports = nb as ts.NamedImports; + + return namedImports.elements + .map((is: ts.ImportSpecifier) => is.propertyName ? is.propertyName.text : is.name.text) + .reduce((acc: {[name: string]: string}, curr: string) => { + acc[curr] = modulePath; + + return acc; + }, {}); + } + } + + return {}; + } else { + // This is of the form `import 'path';`. Nothing to do. + return {}; + } +} + +export function getDecoratorMetadata(source: ts.SourceFile, identifier: string, + module: string): ts.Node[] { + const angularImports: {[name: string]: string} + = findNodes(source, ts.SyntaxKind.ImportDeclaration) + .map((node: ts.ImportDeclaration) => _angularImportsFromNode(node, source)) + .reduce((acc: {[name: string]: string}, current: {[name: string]: string}) => { + for (const key of Object.keys(current)) { + acc[key] = current[key]; + } + + return acc; + }, {}); + + return getSourceNodes(source) + .filter(node => { + return node.kind == ts.SyntaxKind.Decorator + && (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression; + }) + .map(node => (node as ts.Decorator).expression as ts.CallExpression) + .filter(expr => { + if (expr.expression.kind == ts.SyntaxKind.Identifier) { + const id = expr.expression as ts.Identifier; + + return id.text == identifier && angularImports[id.text] === module; + } else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) { + // This covers foo.NgModule when importing * as foo. + const paExpr = expr.expression as ts.PropertyAccessExpression; + // If the left expression is not an identifier, just give up at that point. + if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) { + return false; + } + + const id = paExpr.name.text; + const moduleId = (paExpr.expression as ts.Identifier).text; + + return id === identifier && (angularImports[moduleId + '.'] === module); + } + + return false; + }) + .filter(expr => expr.arguments[0] + && expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression) + .map(expr => expr.arguments[0] as ts.ObjectLiteralExpression); +} + +export function getMetadataField( + node: ts.ObjectLiteralExpression, + metadataField: string, +): ts.ObjectLiteralElement[] { + return node.properties + .filter(prop => ts.isPropertyAssignment(prop)) + // Filter out every fields that's not "metadataField". Also handles string literals + // (but not expressions). + .filter(({ name }: ts.PropertyAssignment) => { + return (ts.isIdentifier(name) || ts.isStringLiteral(name)) + && name.getText() === metadataField; + }); +} + +export function addSymbolToNgModuleMetadata( + source: ts.SourceFile, + ngModulePath: string, + metadataField: string, + symbolName: string, + importPath: string | null = null, +): Change[] { + const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); + let node: any = nodes[0]; // tslint:disable-line:no-any + + // Find the decorator declaration. + if (!node) { + return []; + } + + // Get all the children property assignment of object literals. + const matchingProperties = getMetadataField( + node as ts.ObjectLiteralExpression, + metadataField, + ); + + // Get the last node of the array literal. + if (!matchingProperties) { + return []; + } + if (matchingProperties.length == 0) { + // We haven't found the field in the metadata declaration. Insert a new field. + const expr = node as ts.ObjectLiteralExpression; + let position: number; + let toInsert: string; + if (expr.properties.length == 0) { + position = expr.getEnd() - 1; + toInsert = ` ${metadataField}: [${symbolName}]\n`; + } else { + node = expr.properties[expr.properties.length - 1]; + position = node.getEnd(); + // Get the indentation of the last element, if any. + const text = node.getFullText(source); + const matches = text.match(/^\r?\n\s*/); + if (matches && matches.length > 0) { + toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`; + } else { + toInsert = `, ${metadataField}: [${symbolName}]`; + } + } + if (importPath !== null) { + return [ + new InsertChange(ngModulePath, position, toInsert), + insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath), + ]; + } else { + return [new InsertChange(ngModulePath, position, toInsert)]; + } + } + const assignment = matchingProperties[0] as ts.PropertyAssignment; + + // If it's not an array, nothing we can do really. + if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { + return []; + } + + const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression; + if (arrLiteral.elements.length == 0) { + // Forward the property. + node = arrLiteral; + } else { + node = arrLiteral.elements; + } + + if (!node) { + // tslint:disable-next-line: no-console + console.error('No app module found. Please add your new class to your component.'); + + return []; + } + + if (Array.isArray(node)) { + const nodeArray = node as {} as Array; + const symbolsArray = nodeArray.map(node => node.getText()); + if (symbolsArray.includes(symbolName)) { + return []; + } + + node = node[node.length - 1]; + } + + let toInsert: string; + let position = node.getEnd(); + if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) { + // We haven't found the field in the metadata declaration. Insert a new + // field. + const expr = node as ts.ObjectLiteralExpression; + if (expr.properties.length == 0) { + position = expr.getEnd() - 1; + toInsert = ` ${symbolName}\n`; + } else { + // Get the indentation of the last element, if any. + const text = node.getFullText(source); + if (text.match(/^\r?\r?\n/)) { + toInsert = `,${text.match(/^\r?\n\s*/)[0]}${symbolName}`; + } else { + toInsert = `, ${symbolName}`; + } + } + } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) { + // We found the field but it's empty. Insert it just before the `]`. + position--; + toInsert = `${symbolName}`; + } else { + // Get the indentation of the last element, if any. + const text = node.getFullText(source); + if (text.match(/^\r?\n/)) { + toInsert = `,${text.match(/^\r?\n(\r?)\s*/)[0]}${symbolName}`; + } else { + toInsert = `, ${symbolName}`; + } + } + if (importPath !== null) { + return [ + new InsertChange(ngModulePath, position, toInsert), + insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath), + ]; + } + + return [new InsertChange(ngModulePath, position, toInsert)]; +} + +/** + * Custom function to insert a declaration (component, pipe, directive) + * into NgModule declarations. It also imports the component. + */ +export function addDeclarationToModule(source: ts.SourceFile, + modulePath: string, classifiedName: string, + importPath: string): Change[] { + return addSymbolToNgModuleMetadata( + source, modulePath, 'declarations', classifiedName, importPath); +} + +/** + * Custom function to insert an NgModule into NgModule imports. It also imports the module. + */ +export function addImportToModule(source: ts.SourceFile, + modulePath: string, classifiedName: string, + importPath: string): Change[] { + + return addSymbolToNgModuleMetadata(source, modulePath, 'imports', classifiedName, importPath); +} + +/** + * Custom function to insert an export into NgModule. It also imports it. + */ +export function addExportToModule(source: ts.SourceFile, + modulePath: string, classifiedName: string, + importPath: string): Change[] { + return addSymbolToNgModuleMetadata(source, modulePath, 'exports', classifiedName, importPath); +} + +/** + * Custom function to insert an entryComponent into NgModule. It also imports it. + * @deprecated - Since version 9.0.0 with Ivy, entryComponents is no longer necessary. + */ +export function addEntryComponentToModule(source: ts.SourceFile, + modulePath: string, classifiedName: string, + importPath: string): Change[] { + return addSymbolToNgModuleMetadata( + source, modulePath, + 'entryComponents', classifiedName, importPath, + ); +} + +export function findBootstrapModuleCall(host: Tree, mainPath: string): ts.CallExpression | null { + const mainBuffer = host.read(mainPath); + if (!mainBuffer) { + throw new SchematicsException(`Main file (${mainPath}) not found`); + } + const mainText = mainBuffer.toString('utf-8'); + const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true); + + const allNodes = getSourceNodes(source); + + let bootstrapCall: ts.CallExpression | null = null; + + for (const node of allNodes) { + + let bootstrapCallNode: ts.Node | null = null; + bootstrapCallNode = findNode(node, ts.SyntaxKind.Identifier, 'bootstrapModule'); + + // Walk up the parent until CallExpression is found. + while (bootstrapCallNode && bootstrapCallNode.parent + && bootstrapCallNode.parent.kind !== ts.SyntaxKind.CallExpression) { + + bootstrapCallNode = bootstrapCallNode.parent; + } + + if (bootstrapCallNode !== null && + bootstrapCallNode.parent !== undefined && + bootstrapCallNode.parent.kind === ts.SyntaxKind.CallExpression) { + bootstrapCall = bootstrapCallNode.parent as ts.CallExpression; + break; + } + } + + return bootstrapCall; +} + +export function findBootstrapModulePath(host: Tree, mainPath: string): string { + const bootstrapCall = findBootstrapModuleCall(host, mainPath); + if (!bootstrapCall) { + throw new SchematicsException('Bootstrap call not found'); + } + + const bootstrapModule = bootstrapCall.arguments[0]; + + const mainBuffer = host.read(mainPath); + if (!mainBuffer) { + throw new SchematicsException(`Client app main file (${mainPath}) not found`); + } + const mainText = mainBuffer.toString('utf-8'); + const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true); + const allNodes = getSourceNodes(source); + const bootstrapModuleRelativePath = allNodes + .filter(node => node.kind === ts.SyntaxKind.ImportDeclaration) + .filter(imp => { + return findNode(imp, ts.SyntaxKind.Identifier, bootstrapModule.getText()); + }) + .map((imp: ts.ImportDeclaration) => { + const modulePathStringLiteral = imp.moduleSpecifier as ts.StringLiteral; + + return modulePathStringLiteral.text; + })[0]; + + return bootstrapModuleRelativePath; +} + +export function getAppModulePath(host: Tree, mainPath: string): string { + const moduleRelativePath = findBootstrapModulePath(host, mainPath); + const mainDir = dirname(mainPath); + const modulePath = normalize(`/${mainDir}/${moduleRelativePath}.ts`); + + return modulePath; +} diff --git a/src/cdk/schematics/utils/version-agnostic-typescript.ts b/src/cdk/schematics/utils/version-agnostic-typescript.ts deleted file mode 100644 index ed2504113f85..000000000000 --- a/src/cdk/schematics/utils/version-agnostic-typescript.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @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 - */ - -/** - * This is just a type import and won't be generated in the release output. - * - * Note that we always need to adjust this type import based on the location of the Typescript - * dependency that will be shipped with `@schematics/angular`. - */ -import typescript = require('typescript'); -import {SchematicsException} from '@angular-devkit/schematics'; - -/** - * This is an agnostic re-export of TypeScript. Depending on the context, this module file will - * return the TypeScript version that is being shipped within the `@schematics/angular` package, - * or fall back to the TypeScript version that has been flattened in the node modules. - * - * This is necessary because we parse TypeScript files and pass the resolved AST to the - * `@schematics/angular` package which might have a different TypeScript version installed. - */ -let ts: typeof typescript; - -try { - ts = require('@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'); -} catch { - // Fallback for CLI versions before v8.0.0. The TypeScript dependency has been dropped in - // CLI version v8.0.0 but older CLI versions can still run the latest generation schematics. - // See: https://github.com/angular/angular-cli/commit/bf1c069f73c8e3d4f0e8d584cbfb47c408c1730b - try { - ts = require('@schematics/angular/node_modules/typescript'); - } catch { - try { - ts = require('typescript'); - } catch { - throw new SchematicsException( - 'Error: Could not find a TypeScript version for the ' + - 'schematics. Please report an issue on the Angular Material repository.'); - } - } -} - -export {ts, typescript}; diff --git a/src/cdk/scrolling/scrollable.ts b/src/cdk/scrolling/scrollable.ts index 66464f77640c..ba104d5a2987 100644 --- a/src/cdk/scrolling/scrollable.ts +++ b/src/cdk/scrolling/scrollable.ts @@ -90,8 +90,13 @@ export class CdkScrollable implements OnInit, OnDestroy { const isRtl = this.dir && this.dir.value == 'rtl'; // Rewrite start & end offsets as right or left offsets. - options.left = options.left == null ? (isRtl ? options.end : options.start) : options.left; - options.right = options.right == null ? (isRtl ? options.start : options.end) : options.right; + if (options.left == null) { + options.left = isRtl ? options.end : options.start; + } + + if (options.right == null) { + options.right = isRtl ? options.start : options.end; + } // Rewrite the bottom offset as a top offset. if (options.bottom != null) { diff --git a/src/cdk/scrolling/virtual-for-of.ts b/src/cdk/scrolling/virtual-for-of.ts index ed56e0bf499c..d4f80a50d6c7 100644 --- a/src/cdk/scrolling/virtual-for-of.ts +++ b/src/cdk/scrolling/virtual-for-of.ts @@ -30,7 +30,7 @@ import { TrackByFunction, ViewContainerRef, } from '@angular/core'; -import {Observable, Subject, of as observableOf} from 'rxjs'; +import {Observable, Subject, of as observableOf, isObservable} from 'rxjs'; import {pairwise, shareReplay, startWith, switchMap, takeUntil} from 'rxjs/operators'; import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; @@ -88,11 +88,13 @@ export class CdkVirtualForOf implements CollectionViewer, DoCheck, OnDestroy } set cdkVirtualForOf(value: DataSource | Observable | NgIterable | null | undefined) { this._cdkVirtualForOf = value; - const ds = isDataSource(value) ? value : - // Slice the value if its an NgIterable to ensure we're working with an array. - new ArrayDataSource( - value instanceof Observable ? value : Array.prototype.slice.call(value || [])); - this._dataSourceChanges.next(ds); + if (isDataSource(value)) { + this._dataSourceChanges.next(value); + } else { + // Slice the value if its an NgIterable to ensure we're working with an array. + this._dataSourceChanges.next(new ArrayDataSource( + isObservable(value) ? value : Array.prototype.slice.call(value || []))); + } } _cdkVirtualForOf: DataSource | Observable | NgIterable | null | undefined; diff --git a/src/cdk/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts index cdb4c6543422..27f6e854c202 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts @@ -16,7 +16,15 @@ import { Directive, ViewContainerRef } from '@angular/core'; -import {async, ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing'; +import { + async, + ComponentFixture, + fakeAsync, + flush, + inject, + TestBed, + tick, +} from '@angular/core/testing'; import {animationFrameScheduler, Subject} from 'rxjs'; @@ -74,6 +82,17 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.getViewportSize()).toBe(500); })); + it('should update the viewport size when the page viewport changes', fakeAsync(() => { + finishInit(fixture); + spyOn(viewport, 'checkViewportSize').and.callThrough(); + + dispatchFakeEvent(window, 'resize'); + fixture.detectChanges(); + tick(20); // The resize listener is debounced so we need to flush it. + + expect(viewport.checkViewportSize).toHaveBeenCalled(); + })); + it('should get the rendered range', fakeAsync(() => { finishInit(fixture); diff --git a/src/cdk/scrolling/virtual-scroll-viewport.ts b/src/cdk/scrolling/virtual-scroll-viewport.ts index 0c6a113981c6..04bcf59a027a 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk/scrolling/virtual-scroll-viewport.ts @@ -23,13 +23,20 @@ import { ViewChild, ViewEncapsulation, } from '@angular/core'; -import {animationFrameScheduler, asapScheduler, Observable, Subject, Observer} from 'rxjs'; +import { + animationFrameScheduler, + asapScheduler, + Observable, + Subject, + Observer, + Subscription, +} from 'rxjs'; import {auditTime, startWith, takeUntil} from 'rxjs/operators'; import {ScrollDispatcher} from './scroll-dispatcher'; import {CdkScrollable, ExtendedScrollToOptions} from './scrollable'; import {CdkVirtualForOf} from './virtual-for-of'; import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; - +import {ViewportRuler} from './viewport-ruler'; /** Checks if the given ranges are equal. */ function rangesEqual(r1: ListRange, r2: ListRange): boolean { @@ -142,18 +149,33 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O /** A list of functions to run after the next change detection cycle. */ private _runAfterChangeDetection: Function[] = []; + /** Subscription to changes in the viewport size. */ + private _viewportChanges = Subscription.EMPTY; + constructor(public elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, ngZone: NgZone, @Optional() @Inject(VIRTUAL_SCROLL_STRATEGY) private _scrollStrategy: VirtualScrollStrategy, @Optional() dir: Directionality, - scrollDispatcher: ScrollDispatcher) { + scrollDispatcher: ScrollDispatcher, + /** + * @deprecated `viewportRuler` parameter to become required. + * @breaking-change 11.0.0 + */ + @Optional() viewportRuler?: ViewportRuler) { super(elementRef, scrollDispatcher, ngZone, dir); if (!_scrollStrategy) { throw Error('Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.'); } + + // @breaking-change 11.0.0 Remove null check for `viewportRuler`. + if (viewportRuler) { + this._viewportChanges = viewportRuler.change().subscribe(() => { + this.checkViewportSize(); + }); + } } ngOnInit() { @@ -188,6 +210,7 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O // Complete all subjects this._renderedRangeSubject.complete(); this._detachedSubject.complete(); + this._viewportChanges.unsubscribe(); super.ngOnDestroy(); } @@ -335,8 +358,9 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O * in horizontal mode. */ measureScrollOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number { - return super.measureScrollOffset( - from ? from : this.orientation === 'horizontal' ? 'start' : 'top'); + return from ? + super.measureScrollOffset(from) : + super.measureScrollOffset(this.orientation === 'horizontal' ? 'start' : 'top'); } /** Measure the combined size of all of the rendered items. */ diff --git a/src/cdk/stepper/BUILD.bazel b/src/cdk/stepper/BUILD.bazel index 8219972fa3f4..84bcf8a9ad25 100644 --- a/src/cdk/stepper/BUILD.bazel +++ b/src/cdk/stepper/BUILD.bazel @@ -14,7 +14,6 @@ ng_module( "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/keycodes", - "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/forms", "@npm//rxjs", diff --git a/src/cdk/stepper/stepper-module.ts b/src/cdk/stepper/stepper-module.ts index b51086e9d73f..30f18ecb016c 100644 --- a/src/cdk/stepper/stepper-module.ts +++ b/src/cdk/stepper/stepper-module.ts @@ -8,14 +8,13 @@ import {NgModule} from '@angular/core'; import {CdkStepper, CdkStep} from './stepper'; -import {CommonModule} from '@angular/common'; import {CdkStepLabel} from './step-label'; import {CdkStepperNext, CdkStepperPrevious} from './stepper-button'; import {CdkStepHeader} from './step-header'; import {BidiModule} from '@angular/cdk/bidi'; @NgModule({ - imports: [BidiModule, CommonModule], + imports: [BidiModule], exports: [ CdkStep, CdkStepper, diff --git a/src/cdk/table/BUILD.bazel b/src/cdk/table/BUILD.bazel index 2550f71ff95d..688a2597f6ad 100644 --- a/src/cdk/table/BUILD.bazel +++ b/src/cdk/table/BUILD.bazel @@ -20,7 +20,6 @@ ng_module( "//src/cdk/coercion", "//src/cdk/collections", "//src/cdk/platform", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/cdk/table/table-module.ts b/src/cdk/table/table-module.ts index 8e0f47281d56..882e4c96ff93 100644 --- a/src/cdk/table/table-module.ts +++ b/src/cdk/table/table-module.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {HeaderRowOutlet, DataRowOutlet, CdkTable, FooterRowOutlet} from './table'; import { @@ -42,7 +41,6 @@ const EXPORTED_DECLARATIONS = [ ]; @NgModule({ - imports: [CommonModule], exports: EXPORTED_DECLARATIONS, declarations: EXPORTED_DECLARATIONS diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 0be54297e67f..de403673391d 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -37,7 +37,14 @@ import { ViewContainerRef, ViewEncapsulation } from '@angular/core'; -import {BehaviorSubject, Observable, of as observableOf, Subject, Subscription} from 'rxjs'; +import { + BehaviorSubject, + Observable, + of as observableOf, + Subject, + Subscription, + isObservable, +} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; import {CdkColumnDef} from './cell'; import { @@ -832,7 +839,7 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes if (isDataSource(this.dataSource)) { dataStream = this.dataSource.connect(this); - } else if (this.dataSource instanceof Observable) { + } else if (isObservable(this.dataSource)) { dataStream = this.dataSource; } else if (Array.isArray(this.dataSource)) { dataStream = observableOf(this.dataSource); diff --git a/src/cdk/testing/component-harness.ts b/src/cdk/testing/component-harness.ts index f083aa1a1398..aa762f87c787 100644 --- a/src/cdk/testing/component-harness.ts +++ b/src/cdk/testing/component-harness.ts @@ -507,10 +507,13 @@ function _valueAsString(value: unknown) { } // `JSON.stringify` doesn't handle RegExp properly, so we need a custom replacer. try { - return JSON.stringify(value, (_, v) => - v instanceof RegExp ? `/${v.toString()}/` : - typeof v === 'string' ? v.replace('/\//g', '\\/') : v - ).replace(/"\/\//g, '\\/').replace(/\/\/"/g, '\\/').replace(/\\\//g, '/'); + return JSON.stringify(value, (_, v) => { + if (v instanceof RegExp) { + return `/${v.toString()}/`; + } + + return typeof v === 'string' ? v.replace('/\//g', '\\/') : v; + }).replace(/"\/\//g, '\\/').replace(/\/\/"/g, '\\/').replace(/\\\//g, '/'); } catch { // `JSON.stringify` will throw if the object is cyclical, // in this case the best we can do is report the value as `{...}`. diff --git a/src/cdk/testing/private/e2e/actions.ts b/src/cdk/testing/private/e2e/actions.ts index 1ea20af47a08..b8042126d006 100644 --- a/src/cdk/testing/private/e2e/actions.ts +++ b/src/cdk/testing/private/e2e/actions.ts @@ -14,7 +14,7 @@ import {getElement, FinderResult} from './query'; */ export async function pressKeys(...keys: string[]) { const actions = browser.actions(); - await actions.sendKeys.call(actions, keys).perform(); + await actions.sendKeys(...keys).perform(); } /** diff --git a/src/cdk/testing/protractor/protractor-element.ts b/src/cdk/testing/protractor/protractor-element.ts index 16b6c59ea7d3..7167f45363f0 100644 --- a/src/cdk/testing/protractor/protractor-element.ts +++ b/src/cdk/testing/protractor/protractor-element.ts @@ -146,4 +146,8 @@ export class ProtractorElement implements TestElement { Element.prototype.msMatchesSelector).call(arguments[0], arguments[1]) `, this.element, selector); } + + async isFocused(): Promise { + return this.element.equals(browser.driver.switchTo().activeElement()); + } } diff --git a/src/cdk/testing/test-element.ts b/src/cdk/testing/test-element.ts index a512eedce962..6c0329ea6e4b 100644 --- a/src/cdk/testing/test-element.ts +++ b/src/cdk/testing/test-element.ts @@ -111,4 +111,7 @@ export interface TestElement { /** Checks whether this element matches the given selector. */ matchesSelector(selector: string): Promise; + + /** Checks whether the element is focused. */ + isFocused(): Promise; } diff --git a/src/cdk/testing/test-harnesses.md b/src/cdk/testing/test-harnesses.md index 5ecce2eecdb3..dff10acab9ab 100644 --- a/src/cdk/testing/test-harnesses.md +++ b/src/cdk/testing/test-harnesses.md @@ -73,7 +73,7 @@ Consider a reusable dialog-button component that opens a dialog on click, contai components, each with a corresponding harness: - `MyDialogButton` (composes the `MyButton` and `MyDialog` with a convenient API) - `MyButton` (a simple button component) -- `MyDialog` (a dialog appended to `document.body` by `MyButtonDialog` upon click) +- `MyDialog` (a dialog appended to `document.body` by `MyDialogButton` upon click) The following code loads harnesses for each of these components: @@ -141,20 +141,20 @@ Calls to `getHarness` and `getAllHarnesses` can either take `ComponentHarness` s for a button that has some particular text, etc). The [details of `HarnessPredicate`](#filtering-harness-instances-with-harnesspredicate) are discussed in the [API for component harness authors](#api-for-component-harness-authors); harness authors should -provide convenience methods on their `ComponentHarness` subclass to facilitate creation of +provide convenience methods on their `ComponentHarness` subclass to facilitate the creation of `HarnessPredicate` instances. However, if the harness author's API is not sufficient, they can be created manually. #### Working with asynchronous component harness methods -In order to support both unit and end-to-end tests, and to insulate tests against changes in +To support both unit and end-to-end tests, and to insulate tests against changes in asynchronous behavior, almost all harness methods are asynchronous and return a `Promise`; therefore, the Angular team recommends using [ES2017 `async`/`await` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) to improve the test readability. Note that `await` statements block the execution of your test until the associated `Promise` -resolves. When reading multiple properties off a harness it may not be necessary to block on the +resolves. When reading multiple properties of a harness it may not be necessary to block on the first before asking for the next, in these cases use `Promise.all` to parallelize. For example, consider the following example of reading both the `checked` and `indeterminate` state @@ -184,7 +184,7 @@ common components for a large Angular application. The abstract `ComponentHarness` class is the base class for all component harnesses. To create a custom component harness, extend `ComponentHarness` and implement the static property `hostSelector`. The `hostSelector` property identifies elements in the DOM that match this harness -subclass. In most cases the `hostSelector` should be the same as the `selector` of the corresponding +subclass. In most cases, the `hostSelector` should be the same as the `selector` of the corresponding `Component` or `Directive`. For example, consider a simple popup component: ```ts @@ -285,7 +285,7 @@ unless its an element the component consumer defines directly (e.g. the host ele `TestElement` instances for internal elements leads users to depend on a component's internal DOM structure. -Instead, provide more narrow-focused methods for particular actions the end user will +Instead, provide more narrow-focused methods for particular actions the end-user will take or particular state they may want to check. For example, `MyPopupHarness` could provide methods like `toggle` and `isOpen`: @@ -322,7 +322,7 @@ earlier has an alternate signature that can be used for locating sub-harnesses r | `locatorForOptional(harnessType: ComponentHarnessConstructor): () => Promise` | Creates a function that returns a `Promise` for the first harness matching the given harness type when called. If no matching harness is found, the `Promise` is resolved with `null`. | | `locatorForAll(harnessType: ComponentHarnessConstructor): () => Promise` | Creates a function that returns a `Promise` for a list of all harnesses matching the given harness type when called. | -For example consider a menu build using the popup shown above: +For example, consider a menu build using the popup shown above: ```ts @Component({ @@ -460,7 +460,7 @@ class MyMenuHarness extends ComponentHarness { /** Gets a list of items in the menu, optionally filtered based on the given criteria. */ async getItems(filters: MyMenuItemHarnessFilters = {}): Promise { - const getFilteredItems = this.locatorFor(MyMenuItemHarness.with(filters)); + const getFilteredItems = this.locatorForAll(MyMenuItemHarness.with(filters)); return getFilteredItems(); } @@ -498,10 +498,8 @@ class MyPopupHarness extends ComponentHarness { #### Accessing elements outside of the component's host element -There are times when a component harness might need to access elements outside of it's corresponding -component's host element. A good example of this is components that use the -[CDK overlay](https://material.angular.io/cdk/overlay/overview). The CDK overlay creates an element -that is attached directly to the body, outside of the component's host element. In this case, +There are times when a component harness might need to access elements outside of its corresponding +component's host element. Components that use [CDK overlay](https://material.angular.io/cdk/overlay/overview) serve as examples of this. The CDK overlay creates an element that is attached directly to the body, outside of the component's host element. In this case, `ComponentHarness` provides a method that can be used to get a `LocatorFactory` for the root element of the document. The `LocatorFactory` supports most of the same APIs as the `ComponentHarness` base class, and can then be used to query relative to the document's root element. @@ -537,8 +535,8 @@ subsequent `NgZone` stabilization before animation events are fully flushed. In needed, the `ComponentHarness` offers a `forceStabilize()` method that can be called to do the second round. -Additionally some components may intentionally schedule tasks _outside_ of `NgZone`, this is -typically accomplished by using `NgZone.runOutsideAngular`. In this case the corresponding harness +Additionally, some components may intentionally schedule tasks _outside_ of `NgZone`, this is +typically accomplished by using `NgZone.runOutsideAngular`. In this case, the corresponding harness may need to explicitly wait for tasks outside `NgZone`, as this does not happen automatically. `ComponentHarness` offers a method called `waitForTasksOutsideAngular` for this purpose. @@ -560,7 +558,7 @@ The first step in adding support for a new testing environment is to create a `T implementation. The `TestElement` interface serves as an environment-agnostic representation of a DOM element; it lets harnesses interact with DOM elements regardless of the underlying environment. Because some environments don't support interacting with DOM elements synchronously -(e.g. webdriver), all of `TestElement` methods are asynchronous, returning a `Promise` with the +(e.g. webdriver), all of the `TestElement` methods are asynchronous, returning a `Promise` with the result of the operation. | Method | Description | @@ -599,7 +597,7 @@ Test authors use `HarnessEnvironemnt` to create component harness instances for `HarnessEnvironment` is an abstract class that must be extended to create a concrete subclass for the new environment. When supporting a new test environment, you must create a `HarnessEnvironment` -subclass that add concrete implementations for all abstract members. +subclass that adds concrete implementations for all abstract members. You will notice that `HarnessEnvironment` has a generic type parameter: `HarnessEnvironment`. This parameter, `E`, represents the raw element type of the environment. For example, this parameter diff --git a/src/cdk/testing/testbed/fake-events/event-objects.ts b/src/cdk/testing/testbed/fake-events/event-objects.ts index c0ba61959b3d..36a49cdddd5c 100644 --- a/src/cdk/testing/testbed/fake-events/event-objects.ts +++ b/src/cdk/testing/testbed/fake-events/event-objects.ts @@ -14,7 +14,7 @@ import {ModifierKeys} from '@angular/cdk/testing'; */ export function createMouseEvent(type: string, x = 0, y = 0, button = 0) { const event = document.createEvent('MouseEvent'); - const originalPreventDefault = event.preventDefault; + const originalPreventDefault = event.preventDefault.bind(event); event.initMouseEvent(type, true, /* canBubble */ @@ -39,7 +39,7 @@ export function createMouseEvent(type: string, x = 0, y = 0, button = 0) { // IE won't set `defaultPrevented` on synthetic events so we need to do it manually. event.preventDefault = function() { Object.defineProperty(event, 'defaultPrevented', { get: () => true }); - return originalPreventDefault.apply(this, arguments); + return originalPreventDefault(); }; return event; @@ -85,8 +85,24 @@ export function createKeyboardEvent(type: string, keyCode: number = 0, key: stri } else { // `initKeyboardEvent` expects to receive modifiers as a whitespace-delimited string // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyboardEvent - const modifiersStr = (modifiers.control ? 'Control ' : '' + modifiers.alt ? 'Alt ' : '' + - modifiers.shift ? 'Shift ' : '' + modifiers.meta ? 'Meta' : '').trim(); + let modifiersList = ''; + + if (modifiers.control) { + modifiersList += 'Control '; + } + + if (modifiers.alt) { + modifiersList += 'Alt '; + } + + if (modifiers.shift) { + modifiersList += 'Shift '; + } + + if (modifiers.meta) { + modifiersList += 'Meta '; + } + event.initKeyboardEvent(type, true, /* canBubble */ true, /* cancelable */ @@ -94,7 +110,7 @@ export function createKeyboardEvent(type: string, keyCode: number = 0, key: stri 0, /* char */ key, /* key */ 0, /* location */ - modifiersStr, /* modifiersList */ + modifiersList.trim(), /* modifiersList */ false /* repeat */); } diff --git a/src/cdk/testing/testbed/task-state-zone-interceptor.ts b/src/cdk/testing/testbed/task-state-zone-interceptor.ts index f8b01b58fabb..cc660d0b056c 100644 --- a/src/cdk/testing/testbed/task-state-zone-interceptor.ts +++ b/src/cdk/testing/testbed/task-state-zone-interceptor.ts @@ -8,7 +8,6 @@ import {BehaviorSubject, Observable} from 'rxjs'; import {ProxyZone, ProxyZoneStatic} from './proxy-zone-types'; -import {HasTaskState, Zone, ZoneDelegate} from './zone-types'; /** Current state of the intercepted zone. */ export interface TaskState { @@ -91,7 +90,7 @@ export class TaskStateZoneInterceptor { // the proxy zone keeps track of the previous task state, so we can just pass // this as initial state to the task zone interceptor. const interceptor = new TaskStateZoneInterceptor(zoneSpec.lastTaskState); - const zoneSpecOnHasTask = zoneSpec.onHasTask; + const zoneSpecOnHasTask = zoneSpec.onHasTask.bind(zoneSpec); // We setup the task state interceptor in the `ProxyZone`. Note that we cannot register // the interceptor as a new proxy zone delegate because it would mean that other zone @@ -99,9 +98,9 @@ export class TaskStateZoneInterceptor { // our interceptor. Since we just intend to monitor the task state of the proxy zone, it is // sufficient to just patch the proxy zone. This also avoids that we interfere with the task // queue scheduling logic. - zoneSpec.onHasTask = function() { - zoneSpecOnHasTask.apply(zoneSpec, arguments); - interceptor.onHasTask.apply(interceptor, arguments); + zoneSpec.onHasTask = function(...args: [ZoneDelegate, Zone, Zone, HasTaskState]) { + zoneSpecOnHasTask(...args); + interceptor.onHasTask(...args); }; return zoneSpec[stateObservableSymbol] = interceptor.state; diff --git a/src/cdk/testing/testbed/unit-test-element.ts b/src/cdk/testing/testbed/unit-test-element.ts index 6bbd5d477cc4..a1eded0f7b2a 100644 --- a/src/cdk/testing/testbed/unit-test-element.ts +++ b/src/cdk/testing/testbed/unit-test-element.ts @@ -142,4 +142,9 @@ export class UnitTestElement implements TestElement { return (elementPrototype['matches'] || elementPrototype['msMatchesSelector']) .call(this.element, selector); } + + async isFocused(): Promise { + await this._stabilize(); + return document.activeElement === this.element; + } } diff --git a/src/cdk/testing/testbed/zone-types.d.ts b/src/cdk/testing/testbed/zone-types.d.ts index bb34d2614fcd..d75b69b74f84 100644 --- a/src/cdk/testing/testbed/zone-types.d.ts +++ b/src/cdk/testing/testbed/zone-types.d.ts @@ -14,11 +14,9 @@ * here and use these for our interceptor logic. */ -declare global { - // tslint:disable-next-line:variable-name - const Zone: {current: any}|undefined; +declare interface Zone { + current: any; } - -export type Zone = Object; -export type ZoneDelegate = Object; -export type HasTaskState = {microTask: boolean, macroTask: boolean}; +declare const Zone: Zone | undefined; +declare type ZoneDelegate = Object; +declare type HasTaskState = {microTask: boolean, macroTask: boolean}; diff --git a/src/cdk/testing/tests/BUILD.bazel b/src/cdk/testing/tests/BUILD.bazel index 2daa86c1ca40..f1d088e7c86f 100644 --- a/src/cdk/testing/tests/BUILD.bazel +++ b/src/cdk/testing/tests/BUILD.bazel @@ -39,6 +39,7 @@ ng_test_library( "//src/cdk/testing", "//src/cdk/testing/private", "//src/cdk/testing/testbed", + "@npm//@angular/platform-browser", ], ) diff --git a/src/cdk/testing/tests/protractor.e2e.spec.ts b/src/cdk/testing/tests/protractor.e2e.spec.ts index 09fb528150d9..af5ca877f160 100644 --- a/src/cdk/testing/tests/protractor.e2e.spec.ts +++ b/src/cdk/testing/tests/protractor.e2e.spec.ts @@ -398,6 +398,14 @@ describe('ProtractorHarnessEnvironment', () => { '\n(SubComponentHarness with host element matching selector: "test-sub" satisfying' + ' the constraints: title = /not found/)'); }); + + it('should check if element is focused', async () => { + const button = await harness.button(); + await button.focus(); + expect(await button.isFocused()).toBe(true); + await button.blur(); + expect(await button.isFocused()).toBe(false); + }); }); describe('HarnessPredicate', () => { diff --git a/src/cdk/testing/tests/testbed.spec.ts b/src/cdk/testing/tests/testbed.spec.ts index e1025a5ee47c..ee9cc26e6104 100644 --- a/src/cdk/testing/tests/testbed.spec.ts +++ b/src/cdk/testing/tests/testbed.spec.ts @@ -441,6 +441,14 @@ describe('TestbedHarnessEnvironment', () => { '\n(SubComponentHarness with host element matching selector: "test-sub" satisfying' + ' the constraints: title = /not found/)'); }); + + it('should check if element is focused', async () => { + const button = await harness.button(); + await button.focus(); + expect(await button.isFocused()).toBe(true); + await button.blur(); + expect(await button.isFocused()).toBe(false); + }); }); describe('HarnessPredicate', () => { diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts index 3d8851b103ca..673ec39fc0ea 100644 --- a/src/cdk/text-field/autosize.ts +++ b/src/cdk/text-field/autosize.ts @@ -41,7 +41,7 @@ import {fromEvent, Subject} from 'rxjs'; export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { /** Keep track of the previous textarea value to avoid resizing when the value hasn't changed. */ private _previousValue?: string; - private _initialHeight: string | null; + private _initialHeight: string | undefined; private readonly _destroyed = new Subject(); private _minRows: number; @@ -119,9 +119,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { ngAfterViewInit() { if (this._platform.isBrowser) { // Remember the height which we started with in case autosizing is disabled - // TODO: as any works around `height` being nullable in TS3.6, but non-null in 3.7. - // Remove once on TS3.7. - this._initialHeight = this._textareaElement.style.height as any; + this._initialHeight = this._textareaElement.style.height; this.resizeToFitContent(); @@ -251,11 +249,9 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { reset() { // Do not try to change the textarea, if the initialHeight has not been determined yet // This might potentially remove styles when reset() is called before ngAfterViewInit - if (this._initialHeight === undefined) { - return; + if (this._initialHeight !== undefined) { + this._textareaElement.style.height = this._initialHeight; } - // TODO: "as any" inserted for migration to TS3.7. - this._textareaElement.style.height = this._initialHeight as any; } // In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order diff --git a/src/cdk/tree/BUILD.bazel b/src/cdk/tree/BUILD.bazel index 0f9d4ad6310a..375880d4269b 100644 --- a/src/cdk/tree/BUILD.bazel +++ b/src/cdk/tree/BUILD.bazel @@ -20,7 +20,6 @@ ng_module( "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/collections", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/cdk/tree/control/nested-tree-control.ts b/src/cdk/tree/control/nested-tree-control.ts index 6072468b0add..0cd7ffafcdbb 100644 --- a/src/cdk/tree/control/nested-tree-control.ts +++ b/src/cdk/tree/control/nested-tree-control.ts @@ -5,7 +5,7 @@ * 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 {Observable} from 'rxjs'; +import {Observable, isObservable} from 'rxjs'; import {take, filter} from 'rxjs/operators'; import {BaseTreeControl} from './base-tree-control'; @@ -45,7 +45,7 @@ export class NestedTreeControl extends BaseTreeControl { const childrenNodes = this.getChildren(dataNode); if (Array.isArray(childrenNodes)) { childrenNodes.forEach((child: T) => this._getDescendants(descendants, child)); - } else if (childrenNodes instanceof Observable) { + } else if (isObservable(childrenNodes)) { // TypeScript as of version 3.5 doesn't seem to treat `Boolean` like a function that // returns a `boolean` specifically in the context of `filter`, so we manually clarify that. childrenNodes.pipe(take(1), filter(Boolean as () => boolean)) diff --git a/src/cdk/tree/nested-node.ts b/src/cdk/tree/nested-node.ts index 44f34be21d32..75108b1c2539 100644 --- a/src/cdk/tree/nested-node.ts +++ b/src/cdk/tree/nested-node.ts @@ -15,7 +15,7 @@ import { OnDestroy, QueryList, } from '@angular/core'; -import {Observable} from 'rxjs'; +import {isObservable} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; import {CDK_TREE_NODE_OUTLET_NODE, CdkTreeNodeOutlet} from './outlet'; @@ -70,7 +70,7 @@ export class CdkNestedTreeNode extends CdkTreeNode implements AfterContent const childrenNodes = this._tree.treeControl.getChildren(this.data); if (Array.isArray(childrenNodes)) { this.updateChildrenNodes(childrenNodes as T[]); - } else if (childrenNodes instanceof Observable) { + } else if (isObservable(childrenNodes)) { childrenNodes.pipe(takeUntil(this._destroyed)) .subscribe(result => this.updateChildrenNodes(result)); } diff --git a/src/cdk/tree/tree-module.ts b/src/cdk/tree/tree-module.ts index 3c63a7f8df62..56cc718da0be 100644 --- a/src/cdk/tree/tree-module.ts +++ b/src/cdk/tree/tree-module.ts @@ -7,7 +7,6 @@ */ import {FocusMonitor} from '@angular/cdk/a11y'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {CdkTreeNodeOutlet} from './outlet'; import {CdkTreeNodePadding} from './padding'; @@ -27,7 +26,6 @@ const EXPORTED_DECLARATIONS = [ ]; @NgModule({ - imports: [CommonModule], exports: EXPORTED_DECLARATIONS, declarations: EXPORTED_DECLARATIONS, providers: [FocusMonitor, CdkTreeNodeDef] diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 07e245f89f13..cecba944795b 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -27,7 +27,14 @@ import { ViewEncapsulation, TrackByFunction } from '@angular/core'; -import {BehaviorSubject, Observable, of as observableOf, Subject, Subscription} from 'rxjs'; +import { + BehaviorSubject, + Observable, + of as observableOf, + Subject, + Subscription, + isObservable, +} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; import {TreeControl} from './control/tree-control'; import {CdkTreeNodeDef, CdkTreeNodeOutletContext} from './node'; @@ -194,7 +201,7 @@ export class CdkTree implements AfterContentChecked, CollectionViewer, OnDest if (isDataSource(this._dataSource)) { dataStream = this._dataSource.connect(this); - } else if (this._dataSource instanceof Observable) { + } else if (isObservable(this._dataSource)) { dataStream = this._dataSource; } else if (Array.isArray(this._dataSource)) { dataStream = observableOf(this._dataSource); @@ -366,7 +373,7 @@ export class CdkTreeNode implements FocusableOption, OnDestroy { const childrenNodes = this._tree.treeControl.getChildren(this._data); if (Array.isArray(childrenNodes)) { this._setRoleFromChildren(childrenNodes as T[]); - } else if (childrenNodes instanceof Observable) { + } else if (isObservable(childrenNodes)) { childrenNodes.pipe(takeUntil(this._destroyed)) .subscribe(children => this._setRoleFromChildren(children)); } diff --git a/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-placeholder/cdk-drag-drop-custom-placeholder-example.ts b/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-placeholder/cdk-drag-drop-custom-placeholder-example.ts index 474963cc6262..5e76752a7f76 100644 --- a/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-placeholder/cdk-drag-drop-custom-placeholder-example.ts +++ b/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-placeholder/cdk-drag-drop-custom-placeholder-example.ts @@ -18,7 +18,8 @@ export class CdkDragDropCustomPlaceholderExample { 'Episode V - The Empire Strikes Back', 'Episode VI - Return of the Jedi', 'Episode VII - The Force Awakens', - 'Episode VIII - The Last Jedi' + 'Episode VIII - The Last Jedi', + 'Episode IX – The Rise of Skywalker' ]; drop(event: CdkDragDrop) { diff --git a/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-preview/cdk-drag-drop-custom-preview-example.ts b/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-preview/cdk-drag-drop-custom-preview-example.ts index cd0490bcd1fc..0c39cca050ac 100644 --- a/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-preview/cdk-drag-drop-custom-preview-example.ts +++ b/src/components-examples/cdk/drag-drop/cdk-drag-drop-custom-preview/cdk-drag-drop-custom-preview-example.ts @@ -43,6 +43,10 @@ export class CdkDragDropCustomPreviewExample { { title: 'Episode VIII - The Last Jedi', poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg' + }, + { + title: 'Episode IX – The Rise of Skywalker', + poster: 'https://upload.wikimedia.org/wikipedia/en/a/af/Star_Wars_The_Rise_of_Skywalker_poster.jpg' } ]; // tslint:enable:max-line-length diff --git a/src/components-examples/cdk/drag-drop/cdk-drag-drop-sorting/cdk-drag-drop-sorting-example.ts b/src/components-examples/cdk/drag-drop/cdk-drag-drop-sorting/cdk-drag-drop-sorting-example.ts index e728c97c14f6..d478f587e14b 100644 --- a/src/components-examples/cdk/drag-drop/cdk-drag-drop-sorting/cdk-drag-drop-sorting-example.ts +++ b/src/components-examples/cdk/drag-drop/cdk-drag-drop-sorting/cdk-drag-drop-sorting-example.ts @@ -18,7 +18,8 @@ export class CdkDragDropSortingExample { 'Episode V - The Empire Strikes Back', 'Episode VI - Return of the Jedi', 'Episode VII - The Force Awakens', - 'Episode VIII - The Last Jedi' + 'Episode VIII - The Last Jedi', + 'Episode IX – The Rise of Skywalker' ]; drop(event: CdkDragDrop) { diff --git a/src/components-examples/cdk/platform/cdk-platform-overview/cdk-platform-overview-example.html b/src/components-examples/cdk/platform/cdk-platform-overview/cdk-platform-overview-example.html index f03155b1fff2..d746d38dd00f 100644 --- a/src/components-examples/cdk/platform/cdk-platform-overview/cdk-platform-overview-example.html +++ b/src/components-examples/cdk/platform/cdk-platform-overview/cdk-platform-overview-example.html @@ -6,6 +6,7 @@

Platform information:

Is Webkit: {{platform.WEBKIT}}

Is Trident: {{platform.TRIDENT}}

Is Edge: {{platform.EDGE}}

+

Is Safari: {{platform.SAFARI}}

Supported input types: {{supportedInputTypes}}

Supports passive event listeners: {{supportsPassiveEventListeners}}

Supports scroll behavior: {{supportsScrollBehavior}}

diff --git a/src/components-examples/cdk/stepper/cdk-custom-stepper-without-form/cdk-custom-stepper-without-form-example.ts b/src/components-examples/cdk/stepper/cdk-custom-stepper-without-form/cdk-custom-stepper-without-form-example.ts index cbdafc9fdbf0..906fe521185a 100644 --- a/src/components-examples/cdk/stepper/cdk-custom-stepper-without-form/cdk-custom-stepper-without-form-example.ts +++ b/src/components-examples/cdk/stepper/cdk-custom-stepper-without-form/cdk-custom-stepper-without-form-example.ts @@ -20,10 +20,4 @@ export class CustomStepper extends CdkStepper { onClick(index: number): void { this.selectedIndex = index; } - - // These properties are required so that the Ivy template type checker in strict mode knows - // what kind of values are accepted by the `linear` and `selectedIndex` inputs which - // are inherited from `CdkStepper`. - static ngAcceptInputType_linear: boolean | string | null | undefined; - static ngAcceptInputType_selectedIndex: number | string | null | undefined; } diff --git a/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts b/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts index dbd2261d35a9..230059b1c7cf 100644 --- a/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts @@ -33,7 +33,7 @@ const TREE_DATA: ExampleFlatNode[] = [ expandable: false, level: 2, }, { - name: 'Brussel sprouts', + name: 'Brussels sprouts', expandable: false, level: 2, }, { diff --git a/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts b/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts index b69b44415c4a..a9872ec7ff12 100644 --- a/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts @@ -26,7 +26,7 @@ const TREE_DATA: FoodNode[] = [ name: 'Green', children: [ {name: 'Broccoli'}, - {name: 'Brussel sprouts'}, + {name: 'Brussels sprouts'}, ] }, { name: 'Orange', diff --git a/src/components-examples/example-data.ts b/src/components-examples/example-data.ts index fc50c959bd9a..92e86b9674cb 100644 --- a/src/components-examples/example-data.ts +++ b/src/components-examples/example-data.ts @@ -3,8 +3,8 @@ import {EXAMPLE_COMPONENTS} from './example-module'; /** - * Example data - * with information about Component name, selector, files used in example, and path to examples + * Example data with information about component name, selector, files used in example, and path to + * examples. */ export class ExampleData { diff --git a/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel b/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel new file mode 100644 index 000000000000..ad233d48ec46 --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module") + +ng_module( + name = "mdc-form-field", + srcs = glob(["**/*.ts"]), + assets = glob([ + "**/*.html", + "**/*.css", + ]), + module_name = "@angular/components-examples/material-experimental/mdc-form-field", + deps = [ + "//src/cdk/a11y", + "//src/cdk/coercion", + "//src/material-experimental/mdc-form-field", + "//src/material-experimental/mdc-input", + "//src/material/icon", + "@npm//@angular/common", + "@npm//@angular/forms", + "@npm//rxjs", + ], +) + +filegroup( + name = "source-files", + srcs = glob([ + "**/*.html", + "**/*.css", + "**/*.ts", + ]), +) diff --git a/src/components-examples/material-experimental/mdc-form-field/index.ts b/src/components-examples/material-experimental/mdc-form-field/index.ts new file mode 100644 index 000000000000..fe3638c6f564 --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/index.ts @@ -0,0 +1,31 @@ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {ReactiveFormsModule} from '@angular/forms'; +import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatIconModule} from '@angular/material/icon'; +import { + FormFieldCustomControlExample, + MyTelInput +} from './mdc-form-field-custom-control/form-field-custom-control-example'; + +export { + FormFieldCustomControlExample, + MyTelInput, +}; + +const EXAMPLES = [ + FormFieldCustomControlExample, +]; + +@NgModule({ + imports: [ + CommonModule, + MatFormFieldModule, + MatIconModule, + ReactiveFormsModule, + ], + declarations: [...EXAMPLES, MyTelInput], + exports: EXAMPLES, +}) +export class MdcFormFieldExamplesModule { +} diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.css b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.css new file mode 100644 index 000000000000..3c1dbad8f58c --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.css @@ -0,0 +1,21 @@ +.example-tel-input-container { + display: flex; +} + +.example-tel-input-element { + border: none; + background: none; + padding: 0; + outline: none; + font: inherit; + text-align: center; +} + +.example-tel-input-spacer { + opacity: 0; + transition: opacity 200ms; +} + +:host.example-floating .example-tel-input-spacer { + opacity: 1; +} diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.html b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.html new file mode 100644 index 000000000000..bfa388c2dd7e --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.html @@ -0,0 +1,10 @@ +
+ + + + + +
diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.css b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.html b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.html new file mode 100644 index 000000000000..567da980f964 --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.html @@ -0,0 +1,6 @@ + + Phone number + + phone + Include area code + diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts new file mode 100644 index 000000000000..3e21cb0c4938 --- /dev/null +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts @@ -0,0 +1,155 @@ +import {FocusMonitor} from '@angular/cdk/a11y'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {Component, ElementRef, Input, OnDestroy, Optional, Self} from '@angular/core'; +import {FormBuilder, FormGroup, ControlValueAccessor, NgControl} from '@angular/forms'; +import {MatFormFieldControl} from '@angular/material-experimental/mdc-form-field'; +import {Subject} from 'rxjs'; + +/** @title Form field with custom telephone number input control. */ +@Component({ + selector: 'form-field-custom-control-example', + templateUrl: 'form-field-custom-control-example.html', + styleUrls: ['form-field-custom-control-example.css'], +}) +export class FormFieldCustomControlExample {} + +/** Data structure for holding telephone number. */ +export class MyTel { + constructor(public area: string, public exchange: string, public subscriber: string) {} +} + +/** Custom `MatFormFieldControl` for telephone number input. */ +@Component({ + selector: 'example-tel-input', + templateUrl: 'example-tel-input-example.html', + styleUrls: ['example-tel-input-example.css'], + providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}], + host: { + '[class.example-floating]': 'shouldLabelFloat', + '[id]': 'id', + '[attr.aria-describedby]': 'describedBy', + } +}) +export class MyTelInput implements ControlValueAccessor, MatFormFieldControl, OnDestroy { + static nextId = 0; + + parts: FormGroup; + stateChanges = new Subject(); + focused = false; + errorState = false; + controlType = 'example-tel-input'; + id = `example-tel-input-${MyTelInput.nextId++}`; + describedBy = ''; + onChange = (_: any) => {}; + onTouched = () => {}; + + get empty() { + const {value: {area, exchange, subscriber}} = this.parts; + + return !area && !exchange && !subscriber; + } + + get shouldLabelFloat() { return this.focused || !this.empty; } + + @Input() + get placeholder(): string { return this._placeholder; } + set placeholder(value: string) { + this._placeholder = value; + this.stateChanges.next(); + } + private _placeholder: string; + + @Input() + get required(): boolean { return this._required; } + set required(value: boolean) { + this._required = coerceBooleanProperty(value); + this.stateChanges.next(); + } + private _required = false; + + @Input() + get disabled(): boolean { return this._disabled; } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + this._disabled ? this.parts.disable() : this.parts.enable(); + this.stateChanges.next(); + } + private _disabled = false; + + @Input() + get value(): MyTel | null { + const {value: {area, exchange, subscriber}} = this.parts; + if (area.length === 3 && exchange.length === 3 && subscriber.length === 4) { + return new MyTel(area, exchange, subscriber); + } + return null; + } + set value(tel: MyTel | null) { + const {area, exchange, subscriber} = tel || new MyTel('', '', ''); + this.parts.setValue({area, exchange, subscriber}); + this.stateChanges.next(); + } + + constructor( + formBuilder: FormBuilder, + private _focusMonitor: FocusMonitor, + private _elementRef: ElementRef, + @Optional() @Self() public ngControl: NgControl) { + + this.parts = formBuilder.group({ + area: '', + exchange: '', + subscriber: '', + }); + + _focusMonitor.monitor(_elementRef, true).subscribe(origin => { + if (this.focused && !origin) { + this.onTouched(); + } + this.focused = !!origin; + this.stateChanges.next(); + }); + + if (this.ngControl != null) { + this.ngControl.valueAccessor = this; + } + } + + ngOnDestroy() { + this.stateChanges.complete(); + this._focusMonitor.stopMonitoring(this._elementRef); + } + + setDescribedByIds(ids: string[]) { + this.describedBy = ids.join(' '); + } + + onContainerClick(event: MouseEvent) { + if ((event.target as Element).tagName.toLowerCase() != 'input') { + this._elementRef.nativeElement.querySelector('input')!.focus(); + } + } + + writeValue(tel: MyTel | null): void { + this.value = tel; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + _handleInput(): void { + this.onChange(this.parts.value); + } + + static ngAcceptInputType_disabled: boolean | string | null | undefined; + static ngAcceptInputType_required: boolean | string | null | undefined; +} diff --git a/src/components-examples/material-experimental/popover-edit/BUILD.bazel b/src/components-examples/material-experimental/popover-edit/BUILD.bazel index 67731374733f..a114662e9697 100644 --- a/src/components-examples/material-experimental/popover-edit/BUILD.bazel +++ b/src/components-examples/material-experimental/popover-edit/BUILD.bazel @@ -13,10 +13,13 @@ ng_module( deps = [ "//src/material-experimental/popover-edit", "//src/material/button", + "//src/material/checkbox", "//src/material/icon", "//src/material/input", + "//src/material/list", "//src/material/snack-bar", "//src/material/table", + "@npm//@angular/common", "@npm//@angular/forms", ], ) diff --git a/src/components-examples/material-experimental/popover-edit/index.ts b/src/components-examples/material-experimental/popover-edit/index.ts index b827ab09cad4..8f50c05f5b6d 100644 --- a/src/components-examples/material-experimental/popover-edit/index.ts +++ b/src/components-examples/material-experimental/popover-edit/index.ts @@ -1,9 +1,12 @@ import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {MatPopoverEditModule} from '@angular/material-experimental/popover-edit'; import {MatButtonModule} from '@angular/material/button'; +import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatIconModule} from '@angular/material/icon'; import {MatInputModule} from '@angular/material/input'; +import {MatListModule} from '@angular/material/list'; import {MatSnackBarModule} from '@angular/material/snack-bar'; import {MatTableModule} from '@angular/material/table'; import { @@ -33,9 +36,12 @@ const EXAMPLES = [ @NgModule({ imports: [ + CommonModule, MatButtonModule, + MatCheckboxModule, MatIconModule, MatInputModule, + MatListModule, MatPopoverEditModule, MatSnackBarModule, MatTableModule, diff --git a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.css b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.css index eba60b11b33b..22e16bd83228 100644 --- a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.css +++ b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.css @@ -8,5 +8,5 @@ .example-table td, .example-table th { - width: 25%; + width: 16%; } diff --git a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.html b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.html index 9a92edc7b24b..f622ae4fb336 100644 --- a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.html +++ b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.html @@ -38,9 +38,14 @@ - Name + + Name + Edit enabled + + [matPopoverEdit]="nameEdit" + [matPopoverEditDisabled]="!nameEditEnabled"> {{element.name}} @@ -65,8 +70,47 @@

Name

+ + + + + + +
+ + + + Type + + {{element.type}} + + + +
+
+
+ + + {{type}} + + +
+
+
+
+ - +
@@ -92,4 +136,43 @@

Name

+ + + + Fantasy Counterparts + + {{element.fantasyCounterparts.join(', ')}} + + + +
+
+
+ + + {{fantasyElement}} + + +
+
+ + +
+
+
+
+ + + + + +
diff --git a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.ts b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.ts index 4d1edc82ecc6..c222868d548a 100644 --- a/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.ts +++ b/src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.ts @@ -5,36 +5,66 @@ import {NgForm} from '@angular/forms'; import {MatSnackBar} from '@angular/material/snack-bar'; import {BehaviorSubject, Observable} from 'rxjs'; +export type ElementType = 'Metal' | 'Semimetal' | 'Nonmetal'; + +export type FantasyElement = 'Earth' | 'Water' | 'Wind' | 'Fire' | 'Light' | 'Dark'; + export interface PeriodicElement { name: string; + type: ElementType; position: number; weight: number; symbol: string; + fantasyCounterparts: FantasyElement[]; } const ELEMENT_DATA: PeriodicElement[] = [ - {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, - {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, - {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'}, - {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'}, - {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'}, - {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}, - {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'}, - {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'}, - {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'}, - {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, - {position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'}, - {position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'}, - {position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'}, - {position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'}, - {position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'}, - {position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'}, - {position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'}, - {position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'}, - {position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'}, - {position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'}, + {position: 1, name: 'Hydrogen', type: 'Nonmetal', weight: 1.0079, symbol: 'H', + fantasyCounterparts: ['Fire', 'Wind', 'Light']}, + {position: 2, name: 'Helium', type: 'Nonmetal', weight: 4.0026, symbol: 'He', + fantasyCounterparts: ['Wind', 'Light']}, + {position: 3, name: 'Lithium', type: 'Metal', weight: 6.941, symbol: 'Li', + fantasyCounterparts: []}, + {position: 4, name: 'Beryllium', type: 'Metal', weight: 9.0122, symbol: 'Be', + fantasyCounterparts: []}, + {position: 5, name: 'Boron', type: 'Semimetal', weight: 10.811, symbol: 'B', + fantasyCounterparts: []}, + {position: 6, name: 'Carbon', type: 'Nonmetal', weight: 12.0107, symbol: 'C', + fantasyCounterparts: ['Earth', 'Dark']}, + {position: 7, name: 'Nitrogen', type: 'Nonmetal', weight: 14.0067, symbol: 'N', + fantasyCounterparts: ['Wind']}, + {position: 8, name: 'Oxygen', type: 'Nonmetal', weight: 15.9994, symbol: 'O', + fantasyCounterparts: ['Fire', 'Water', 'Wind']}, + {position: 9, name: 'Fluorine', type: 'Nonmetal', weight: 18.9984, symbol: 'F', + fantasyCounterparts: []}, + {position: 10, name: 'Neon', type: 'Nonmetal', weight: 20.1797, symbol: 'Ne', + fantasyCounterparts: ['Light']}, + {position: 11, name: 'Sodium', type: 'Metal', weight: 22.9897, symbol: 'Na', + fantasyCounterparts: ['Earth', 'Water']}, + {position: 12, name: 'Magnesium', type: 'Metal', weight: 24.305, symbol: 'Mg', + fantasyCounterparts: []}, + {position: 13, name: 'Aluminum', type: 'Metal', weight: 26.9815, symbol: 'Al', + fantasyCounterparts: []}, + {position: 14, name: 'Silicon', type: 'Semimetal', weight: 28.0855, symbol: 'Si', + fantasyCounterparts: []}, + {position: 15, name: 'Phosphorus', type: 'Nonmetal', weight: 30.9738, symbol: 'P', + fantasyCounterparts: []}, + {position: 16, name: 'Sulfur', type: 'Nonmetal', weight: 32.065, symbol: 'S', + fantasyCounterparts: []}, + {position: 17, name: 'Chlorine', type: 'Nonmetal', weight: 35.453, symbol: 'Cl', + fantasyCounterparts: []}, + {position: 18, name: 'Argon', type: 'Nonmetal', weight: 39.948, symbol: 'Ar', + fantasyCounterparts: []}, + {position: 19, name: 'Potassium', type: 'Metal', weight: 39.0983, symbol: 'K', + fantasyCounterparts: []}, + {position: 20, name: 'Calcium', type: 'Metal', weight: 40.078, symbol: 'Ca', + fantasyCounterparts: []}, ]; +const TYPES: readonly ElementType[] = ['Metal', 'Semimetal', 'Nonmetal']; +const FANTASY_ELEMENTS: readonly FantasyElement[] = + ['Earth', 'Water', 'Wind', 'Fire', 'Light', 'Dark']; + /** * @title Material Popover Edit on a Material data-table */ @@ -44,11 +74,19 @@ const ELEMENT_DATA: PeriodicElement[] = [ templateUrl: 'popover-edit-mat-table-example.html', }) export class PopoverEditMatTableExample { - displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; + displayedColumns: string[] = + ['position', 'name', 'type', 'weight', 'symbol', 'fantasyCounterpart']; dataSource = new ExampleDataSource(); + nameEditEnabled = true; + + readonly TYPES = TYPES; + readonly FANTASY_ELEMENTS = FANTASY_ELEMENTS; + readonly nameValues = new FormValueContainer(); readonly weightValues = new FormValueContainer(); + readonly typeValues = new FormValueContainer(); + readonly fantasyValues = new FormValueContainer(); constructor(private readonly _snackBar: MatSnackBar) {} @@ -64,6 +102,18 @@ export class PopoverEditMatTableExample { element.weight = f.value.weight; } + onSubmitType(element: PeriodicElement, f: NgForm) { + if (!f.valid) { return; } + + element.type = f.value.type[0]; + } + + onSubmitFantasyCounterparts(element: PeriodicElement, f: NgForm) { + if (!f.valid) { return; } + + element.fantasyCounterparts = f.value.fantasyCounterparts; + } + goodJob(element: PeriodicElement) { this._snackBar.open(`Way to go, ${element.name}!`, undefined, {duration: 2000}); } diff --git a/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts b/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts index 43212fe53469..9a8d19f170e2 100644 --- a/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts +++ b/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {DataSource} from '@angular/cdk/collections'; import {NgForm} from '@angular/forms'; import {BehaviorSubject, Observable} from 'rxjs'; @@ -40,6 +40,7 @@ const ELEMENT_DATA: PeriodicElement[] = [ selector: 'popover-edit-tab-out-mat-table-example', styleUrls: ['popover-edit-tab-out-mat-table-example.css'], templateUrl: 'popover-edit-tab-out-mat-table-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PopoverEditTabOutMatTableExample { displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; diff --git a/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.html b/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.html index ab54ee9d5b2a..455d42ef963b 100644 --- a/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.html +++ b/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.html @@ -1,11 +1,7 @@
- + Assignee + {{option.name}} diff --git a/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.ts b/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.ts index 98e0f85de5e0..797c98c05281 100644 --- a/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.ts +++ b/src/components-examples/material/autocomplete/autocomplete-display/autocomplete-display-example.ts @@ -34,7 +34,7 @@ export class AutocompleteDisplayExample implements OnInit { } displayFn(user: User): string { - return user.name; + return user && user.name ? user.name : ''; } private _filter(name: string): User[] { diff --git a/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.html b/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.html index a1b4e5d50744..e7464e2f2619 100644 --- a/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.html +++ b/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.html @@ -1,5 +1,6 @@ - + Choose a date + keyboard_arrow_down diff --git a/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.html b/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.html index fbba9f6a1693..036d3945ecaf 100644 --- a/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.html +++ b/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.html b/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.html index ad3b0755167b..4d4579994f55 100644 --- a/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.html +++ b/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.html @@ -1,6 +1,7 @@

- + Completely disabled + @@ -8,7 +9,8 @@

- + Popup disabled + @@ -16,7 +18,8 @@

- + Input disabled + diff --git a/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.html b/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.html index 1935c718218c..d1bd110a4200 100644 --- a/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.html +++ b/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.html @@ -1,5 +1,6 @@ - Input & change events + diff --git a/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.html b/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.html index 6a44973b048e..9f14273d83b9 100644 --- a/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.html +++ b/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.html b/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.html index 4868f2dc21ed..2d02a2e080cc 100644 --- a/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.html +++ b/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.html @@ -1,5 +1,6 @@ - + Verbose datepicker + diff --git a/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.html b/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.html index f92432a48237..5cd0d78a18ef 100644 --- a/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.html +++ b/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.html @@ -1,5 +1,6 @@ - + Different locale + diff --git a/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.html b/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.html index 4ee60cdfdd86..141f5291013c 100644 --- a/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.html +++ b/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-moment/datepicker-moment-example.html b/src/components-examples/material/datepicker/datepicker-moment/datepicker-moment-example.html index a6ae46cc7d2d..7ece4c8cbf8c 100644 --- a/src/components-examples/material/datepicker/datepicker-moment/datepicker-moment-example.html +++ b/src/components-examples/material/datepicker/datepicker-moment/datepicker-moment-example.html @@ -1,5 +1,6 @@ - + Moment.js datepicker + diff --git a/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.html b/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.html index f82880547b24..ae8c5b302792 100644 --- a/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.html +++ b/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.html b/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.html index e10afcab6d26..867acb2e5933 100644 --- a/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.html +++ b/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.html b/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.html index 70a7e61e264b..36e5702658a1 100644 --- a/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.html +++ b/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.html @@ -1,5 +1,6 @@ - + Choose a date + diff --git a/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.html b/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.html index fbd5f591d1f1..75114c9a5cd3 100644 --- a/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.html +++ b/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.html @@ -1,18 +1,21 @@ - + Angular forms + - Angular forms (w/ deserialization) + - + Value binding + diff --git a/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.html b/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.html index 4e972fce9dd6..597cbc864a15 100644 --- a/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.html +++ b/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.html @@ -1,5 +1,6 @@ - + Month and Year + Hi {{data.name}}

What's your favorite animal?

+ Favorite Animal
diff --git a/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.html b/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.html index 00e14854f5ca..cdc9cdded3ec 100644 --- a/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.html +++ b/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.html @@ -1,7 +1,8 @@
  1. - + What's your name? +
  2. diff --git a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css index 8536de1b1880..7628b04917c6 100644 --- a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css +++ b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css @@ -11,3 +11,7 @@ justify-content: space-between; align-items: center; } + +.example-headers-align .mat-form-field + .mat-form-field { + margin-left: 8px; +} diff --git a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html index 0231b80d874f..2c314cd946c7 100644 --- a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html +++ b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html @@ -15,11 +15,13 @@ - + First name + - + Age + @@ -36,7 +38,8 @@ - + Country + @@ -52,7 +55,8 @@ - + Date + diff --git a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.css b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.css index 7432308753e6..efcf862a8a38 100644 --- a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.css +++ b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.css @@ -1 +1,3 @@ -/** No CSS for this example */ +.mat-form-field + .mat-form-field { + margin-left: 8px; +} diff --git a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.html b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.html index 8ba383c31746..76c98d933393 100644 --- a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.html +++ b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.html @@ -10,11 +10,13 @@ - + First name + - + Age + - + First name + - + Age + @@ -35,7 +37,8 @@ - + Country + @@ -56,7 +59,8 @@ - + Date + diff --git a/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.html b/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.html index 26df09b44d4e..0598916caa69 100644 --- a/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.html +++ b/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.html @@ -1,5 +1,6 @@ - - + + Phone number + phone Include area code diff --git a/src/components-examples/material/form-field/form-field-error/form-field-error-example.css b/src/components-examples/material/form-field/form-field-error/form-field-error-example.css index cfd4505c7c7a..9041e710f781 100644 --- a/src/components-examples/material/form-field/form-field-error/form-field-error-example.css +++ b/src/components-examples/material/form-field/form-field-error/form-field-error-example.css @@ -1,8 +1,3 @@ -.example-container { - display: flex; - flex-direction: column; -} - -.example-container > * { - width: 100%; +.example-container .mat-form-field + .mat-form-field { + margin-left: 8px; } diff --git a/src/components-examples/material/form-field/form-field-error/form-field-error-example.html b/src/components-examples/material/form-field/form-field-error/form-field-error-example.html index 4b06c9b07b86..ede478b60336 100644 --- a/src/components-examples/material/form-field/form-field-error/form-field-error-example.html +++ b/src/components-examples/material/form-field/form-field-error/form-field-error-example.html @@ -1,6 +1,7 @@
    - - + + Enter your email + {{getErrorMessage()}}
    diff --git a/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts b/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts index 042f95019e99..5f452ea15e47 100644 --- a/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts +++ b/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts @@ -11,8 +11,10 @@ export class FormFieldErrorExample { email = new FormControl('', [Validators.required, Validators.email]); getErrorMessage() { - return this.email.hasError('required') ? 'You must enter a value' : - this.email.hasError('email') ? 'Not a valid email' : - ''; + if (this.email.hasError('required')) { + return 'You must enter a value'; + } + + return this.email.hasError('email') ? 'Not a valid email' : ''; } } diff --git a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.css b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.css index cfd4505c7c7a..9041e710f781 100644 --- a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.css +++ b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.css @@ -1,8 +1,3 @@ -.example-container { - display: flex; - flex-direction: column; -} - -.example-container > * { - width: 100%; +.example-container .mat-form-field + .mat-form-field { + margin-left: 8px; } diff --git a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.html b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.html index a402d988c0fa..0adaf41936f2 100644 --- a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.html +++ b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.html @@ -1,11 +1,13 @@
    - - + + Enter some input + {{input.value?.length || 0}}/10 - - + + Select me + Option Here's the dropdown arrow ^ diff --git a/src/components-examples/material/form-field/form-field-label/form-field-label-example.css b/src/components-examples/material/form-field/form-field-label/form-field-label-example.css index dfe4240b6b2c..5a8c45d9c5e6 100644 --- a/src/components-examples/material/form-field/form-field-label/form-field-label-example.css +++ b/src/components-examples/material/form-field/form-field-label/form-field-label-example.css @@ -1,10 +1,9 @@ -.example-container { - display: flex; - flex-direction: column; +.example-container .mat-form-field + .mat-form-field { + margin-left: 8px; } -.example-container > * { - width: 100%; +.example-container .mat-form-field { + width: 220px; } .example-container form { diff --git a/src/components-examples/material/form-field/form-field-label/form-field-label-example.html b/src/components-examples/material/form-field/form-field-label/form-field-label-example.html index 4d0b41243456..443798045b97 100644 --- a/src/components-examples/material/form-field/form-field-label/form-field-label-example.html +++ b/src/components-examples/material/form-field/form-field-label/form-field-label-example.html @@ -1,34 +1,33 @@
    - - Hide required marker + + Hide required marker
    - + Auto Always - Never
    - - - - + + + - - Both a label and a placeholder - - + + Both a label and a placeholder + + - - - -- None -- - Option - - favorite Fancy label - + + + -- None -- + Option + + favorite Fancy label + +
    diff --git a/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts b/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts index ee03367d79bb..b2dfe0865c5f 100644 --- a/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts +++ b/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {FormBuilder, FormGroup} from '@angular/forms'; +import {FormBuilder, FormControl, FormGroup} from '@angular/forms'; /** @title Form field with label */ @Component({ @@ -9,11 +9,13 @@ import {FormBuilder, FormGroup} from '@angular/forms'; }) export class FormFieldLabelExample { options: FormGroup; + hideRequiredControl = new FormControl(false); + floatLabelControl = new FormControl('auto'); constructor(fb: FormBuilder) { this.options = fb.group({ - hideRequired: false, - floatLabel: 'auto', + hideRequired: this.hideRequiredControl, + floatLabel: this.floatLabelControl, }); } } diff --git a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.css b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.css index cfd4505c7c7a..7432308753e6 100644 --- a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.css +++ b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.css @@ -1,8 +1 @@ -.example-container { - display: flex; - flex-direction: column; -} - -.example-container > * { - width: 100%; -} +/** No CSS for this example */ diff --git a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.html b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.html index 6427e7df313b..3e265d025d4a 100644 --- a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.html +++ b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.html @@ -1,15 +1,18 @@
    - - + + Input + - - - - - - - +
    + + Select + Option +
    + + Textarea + +
    diff --git a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.css b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.css index 00797da0b318..e87a3ce39c4d 100644 --- a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.css +++ b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.css @@ -1,10 +1,5 @@ -.example-container { - display: flex; - flex-direction: column; -} - -.example-container > * { - width: 100%; +.example-container .mat-form-field + .mat-form-field { + margin-left: 8px; } .example-right-align { diff --git a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.html b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.html index 5edb2994ff69..396ab720e3b3 100644 --- a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.html +++ b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.html @@ -1,13 +1,15 @@
    - - + + Enter your password + - - + + Amount + .00 diff --git a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.css b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.css index cfd4505c7c7a..9041e710f781 100644 --- a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.css +++ b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.css @@ -1,8 +1,3 @@ -.example-container { - display: flex; - flex-direction: column; -} - -.example-container > * { - width: 100%; +.example-container .mat-form-field + .mat-form-field { + margin-left: 8px; } diff --git a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.html b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.html index 5c614184e1a1..d3cef345b3b9 100644 --- a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.html +++ b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.html @@ -1,14 +1,17 @@
    - - + + Color + Primary Accent Warn - - - Min size: 10px + + Font size + + px + Min size: 10px diff --git a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.ts b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.ts index 101e27f003d7..ee4573351ac7 100644 --- a/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.ts +++ b/src/components-examples/material/form-field/form-field-theming/form-field-theming-example.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; /** @title Form field theming */ @Component({ @@ -9,15 +9,17 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms'; }) export class FormFieldThemingExample { options: FormGroup; + colorControl = new FormControl('primary'); + fontSizeControl = new FormControl(16, Validators.min(10)); constructor(fb: FormBuilder) { this.options = fb.group({ - color: 'primary', - fontSize: [16, Validators.min(10)], + color: this.colorControl, + fontSize: this.fontSizeControl, }); } getFontSize() { - return Math.max(10, this.options.value.fontSize); + return Math.max(10, this.fontSizeControl.value); } } diff --git a/src/components-examples/material/input/input-clearable/input-clearable-example.html b/src/components-examples/material/input/input-clearable/input-clearable-example.html index 4a135e1d60ff..6b548dbeda4a 100644 --- a/src/components-examples/material/input/input-clearable/input-clearable-example.html +++ b/src/components-examples/material/input/input-clearable/input-clearable-example.html @@ -1,5 +1,6 @@ - + Clearable input + diff --git a/src/components-examples/material/input/input-error-state-matcher/input-error-state-matcher-example.html b/src/components-examples/material/input/input-error-state-matcher/input-error-state-matcher-example.html index a9a0244209dd..29274d958cae 100644 --- a/src/components-examples/material/input/input-error-state-matcher/input-error-state-matcher-example.html +++ b/src/components-examples/material/input/input-error-state-matcher/input-error-state-matcher-example.html @@ -1,7 +1,8 @@
    - + Email + Errors appear instantly! Please enter a valid email address diff --git a/src/components-examples/material/input/input-errors/input-errors-example.html b/src/components-examples/material/input/input-errors/input-errors-example.html index cf241eef4ffd..b2912234e3eb 100644 --- a/src/components-examples/material/input/input-errors/input-errors-example.html +++ b/src/components-examples/material/input/input-errors/input-errors-example.html @@ -1,6 +1,7 @@ - + Email + Please enter a valid email address diff --git a/src/components-examples/material/input/input-form/input-form-example.html b/src/components-examples/material/input/input-form/input-form-example.html index 8cf31e056b62..eb1dcb7c5ced 100644 --- a/src/components-examples/material/input/input-form/input-form-example.html +++ b/src/components-examples/material/input/input-form/input-form-example.html @@ -1,35 +1,43 @@ - + Company (disabled) +
    - + First name + - + Long Last Name That Will Be Truncated +

    - + Address + - + Address 2 +

    - + City + - + State + - + Postal Code + {{postalCode.value.length}} / 5
    diff --git a/src/components-examples/material/input/input-hint/input-hint-example.html b/src/components-examples/material/input/input-hint/input-hint-example.html index b7595cfa636b..cae3c592b758 100644 --- a/src/components-examples/material/input/input-hint/input-hint-example.html +++ b/src/components-examples/material/input/input-hint/input-hint-example.html @@ -1,9 +1,8 @@ - - + Message + Don't disclose personal info {{message.value.length}} / 256 - diff --git a/src/components-examples/material/input/input-overview/input-overview-example.html b/src/components-examples/material/input/input-overview/input-overview-example.html index 5f3a09ceebc4..0ace199c045b 100644 --- a/src/components-examples/material/input/input-overview/input-overview-example.html +++ b/src/components-examples/material/input/input-overview/input-overview-example.html @@ -1,9 +1,11 @@
    - + Favorite food + - + Leave a comment +
    diff --git a/src/components-examples/material/input/input-prefix-suffix/input-prefix-suffix-example.html b/src/components-examples/material/input/input-prefix-suffix/input-prefix-suffix-example.html index 7bff7e420fcd..5dfb7f321fe5 100644 --- a/src/components-examples/material/input/input-prefix-suffix/input-prefix-suffix-example.html +++ b/src/components-examples/material/input/input-prefix-suffix/input-prefix-suffix-example.html @@ -1,9 +1,8 @@
    - + Telephone +1   - + mode_edit -
    diff --git a/src/components-examples/material/list/index.ts b/src/components-examples/material/list/index.ts index eea8dd9e66de..a4bac254fe6d 100644 --- a/src/components-examples/material/list/index.ts +++ b/src/components-examples/material/list/index.ts @@ -5,17 +5,20 @@ import {MatListModule} from '@angular/material/list'; import {ListOverviewExample} from './list-overview/list-overview-example'; import {ListSectionsExample} from './list-sections/list-sections-example'; import {ListSelectionExample} from './list-selection/list-selection-example'; +import {ListSingleSelectionExample} from './list-single-selection/list-single-selection-example'; export { ListOverviewExample, ListSectionsExample, ListSelectionExample, + ListSingleSelectionExample, }; const EXAMPLES = [ ListOverviewExample, ListSectionsExample, ListSelectionExample, + ListSingleSelectionExample, ]; @NgModule({ diff --git a/src/components-examples/material/list/list-single-selection/list-single-selection-example.css b/src/components-examples/material/list/list-single-selection/list-single-selection-example.css new file mode 100644 index 000000000000..7949471cec95 --- /dev/null +++ b/src/components-examples/material/list/list-single-selection/list-single-selection-example.css @@ -0,0 +1 @@ +/** No styles for this example. */ diff --git a/src/components-examples/material/list/list-single-selection/list-single-selection-example.html b/src/components-examples/material/list/list-single-selection/list-single-selection-example.html new file mode 100644 index 000000000000..29cd74bbe71d --- /dev/null +++ b/src/components-examples/material/list/list-single-selection/list-single-selection-example.html @@ -0,0 +1,9 @@ + + + {{shoe}} + + + +

    + Option selected: {{shoes.selectedOptions.selected}} +

    diff --git a/src/components-examples/material/list/list-single-selection/list-single-selection-example.ts b/src/components-examples/material/list/list-single-selection/list-single-selection-example.ts new file mode 100644 index 000000000000..8e1ee0de2ead --- /dev/null +++ b/src/components-examples/material/list/list-single-selection/list-single-selection-example.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + +/** + * @title List with single selection + */ +@Component({ + selector: 'list-single-selection-example', + styleUrls: ['list-single-selection-example.css'], + templateUrl: 'list-single-selection-example.html', +}) +export class ListSingleSelectionExample { + typesOfShoes: string[] = ['Boots', 'Clogs', 'Loafers', 'Moccasins', 'Sneakers']; +} diff --git a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.css b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.css index ece1d0db513c..9cf43ddbc1f3 100644 --- a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.css +++ b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.css @@ -1,3 +1,3 @@ -mat-form-field { +.mat-form-field { margin-right: 12px; } diff --git a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html index 665cacfab07c..8cf07ee98d39 100644 --- a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html +++ b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html @@ -1,23 +1,22 @@ - List length: - + List length + - Page size: - + Page size + - Page size options: - + Page size options + + [pageSize]="pageSize" + [pageSizeOptions]="pageSizeOptions" + (page)="pageEvent = $event">
    diff --git a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts index a02b6a1d03bf..b46462fda863 100644 --- a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts +++ b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts @@ -19,6 +19,8 @@ export class PaginatorConfigurableExample { pageEvent: PageEvent; setPageSizeOptions(setPageSizeOptionsInput: string) { - this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str); + if (setPageSizeOptionsInput) { + this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str); + } } } diff --git a/src/components-examples/material/select/index.ts b/src/components-examples/material/select/index.ts index d352a25fbc21..2f494ed8fb91 100644 --- a/src/components-examples/material/select/index.ts +++ b/src/components-examples/material/select/index.ts @@ -18,6 +18,8 @@ import {SelectOverviewExample} from './select-overview/select-overview-example'; import {SelectPanelClassExample} from './select-panel-class/select-panel-class-example'; import {SelectResetExample} from './select-reset/select-reset-example'; import {SelectValueBindingExample} from './select-value-binding/select-value-binding-example'; +import {SelectReactiveFormExample} from './select-reactive-form/select-reactive-form-example'; +import {SelectInitialValueExample} from './select-initial-value/select-initial-value-example'; export { SelectCustomTriggerExample, @@ -25,11 +27,13 @@ export { SelectErrorStateMatcherExample, SelectFormExample, SelectHintErrorExample, + SelectInitialValueExample, SelectMultipleExample, SelectNoRippleExample, SelectOptgroupExample, SelectOverviewExample, SelectPanelClassExample, + SelectReactiveFormExample, SelectResetExample, SelectValueBindingExample, }; @@ -40,11 +44,13 @@ const EXAMPLES = [ SelectErrorStateMatcherExample, SelectFormExample, SelectHintErrorExample, + SelectInitialValueExample, SelectMultipleExample, SelectNoRippleExample, SelectOptgroupExample, SelectOverviewExample, SelectPanelClassExample, + SelectReactiveFormExample, SelectResetExample, SelectValueBindingExample, ]; diff --git a/src/components-examples/material/select/select-custom-trigger/select-custom-trigger-example.html b/src/components-examples/material/select/select-custom-trigger/select-custom-trigger-example.html index 9c6f8960bb31..5699c30cbd0f 100644 --- a/src/components-examples/material/select/select-custom-trigger/select-custom-trigger-example.html +++ b/src/components-examples/material/select/select-custom-trigger/select-custom-trigger-example.html @@ -1,5 +1,6 @@ - + Toppings + {{toppings.value ? toppings.value[0] : ''}} diff --git a/src/components-examples/material/select/select-form/select-form-example.ts b/src/components-examples/material/select/select-form/select-form-example.ts index b72b6581f9ea..374da926e648 100644 --- a/src/components-examples/material/select/select-form/select-form-example.ts +++ b/src/components-examples/material/select/select-form/select-form-example.ts @@ -1,11 +1,11 @@ import {Component} from '@angular/core'; -export interface Food { +interface Food { value: string; viewValue: string; } -export interface Car { +interface Car { value: string; viewValue: string; } diff --git a/src/components-examples/material/select/select-hint-error/select-hint-error-example.ts b/src/components-examples/material/select/select-hint-error/select-hint-error-example.ts index b5ac67592419..36726d3604ea 100644 --- a/src/components-examples/material/select/select-hint-error/select-hint-error-example.ts +++ b/src/components-examples/material/select/select-hint-error/select-hint-error-example.ts @@ -1,7 +1,7 @@ import {Component} from '@angular/core'; import {FormControl, Validators} from '@angular/forms'; -export interface Animal { +interface Animal { name: string; sound: string; } @@ -13,7 +13,7 @@ export interface Animal { styleUrls: ['select-hint-error-example.css'], }) export class SelectHintErrorExample { - animalControl = new FormControl('', [Validators.required]); + animalControl = new FormControl('', Validators.required); selectFormControl = new FormControl('', Validators.required); animals: Animal[] = [ {name: 'Dog', sound: 'Woof!'}, diff --git a/src/components-examples/material/select/select-initial-value/select-initial-value-example.css b/src/components-examples/material/select/select-initial-value/select-initial-value-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/components-examples/material/select/select-initial-value/select-initial-value-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/components-examples/material/select/select-initial-value/select-initial-value-example.html b/src/components-examples/material/select/select-initial-value/select-initial-value-example.html new file mode 100644 index 000000000000..a1cf4d269b7a --- /dev/null +++ b/src/components-examples/material/select/select-initial-value/select-initial-value-example.html @@ -0,0 +1,20 @@ +

    Basic mat-select with initial value

    + + Favorite Food + + + {{ option.viewValue }} + + +

    You selected: {{selectedFood}}

    + +

    Basic native select with initial value

    + + Favorite Car + + +

    You selected: {{selectedCar}}

    diff --git a/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts b/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts new file mode 100644 index 000000000000..23636a506fe3 --- /dev/null +++ b/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts @@ -0,0 +1,38 @@ +import {Component} from '@angular/core'; + +interface Food { + value: string; + viewValue: string; +} + +interface Car { + value: string; + viewValue: string; +} + +/** + * @title Basic select with initial value and no form + */ +@Component({ + selector: 'select-initial-value-example', + templateUrl: 'select-initial-value-example.html', + styleUrls: ['select-initial-value-example.css'], +}) +export class SelectInitialValueExample { + foods: Food[] = [ + {value: 'steak-0', viewValue: 'Steak'}, + {value: 'pizza-1', viewValue: 'Pizza'}, + {value: 'tacos-2', viewValue: 'Tacos'} + ]; + cars: Car[] = [ + {value: 'ford', viewValue: 'Ford'}, + {value: 'chevrolet', viewValue: 'Chevrolet'}, + {value: 'dodge', viewValue: 'Dodge'} + ]; + selectedFood = this.foods[2].value; + selectedCar = this.cars[0].value; + + selectCar(event: Event) { + this.selectedCar = (event.target as HTMLSelectElement).value; + } +} diff --git a/src/components-examples/material/select/select-optgroup/select-optgroup-example.ts b/src/components-examples/material/select/select-optgroup/select-optgroup-example.ts index 2624e8a5643e..a73f2f29d11e 100644 --- a/src/components-examples/material/select/select-optgroup/select-optgroup-example.ts +++ b/src/components-examples/material/select/select-optgroup/select-optgroup-example.ts @@ -1,12 +1,12 @@ import {Component} from '@angular/core'; import {FormControl} from '@angular/forms'; -export interface Pokemon { +interface Pokemon { value: string; viewValue: string; } -export interface PokemonGroup { +interface PokemonGroup { disabled?: boolean; name: string; pokemon: Pokemon[]; diff --git a/src/components-examples/material/select/select-overview/select-overview-example.ts b/src/components-examples/material/select/select-overview/select-overview-example.ts index 3f306bd1a44c..eddb8ca60673 100644 --- a/src/components-examples/material/select/select-overview/select-overview-example.ts +++ b/src/components-examples/material/select/select-overview/select-overview-example.ts @@ -1,6 +1,6 @@ import {Component} from '@angular/core'; -export interface Food { +interface Food { value: string; viewValue: string; } diff --git a/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.css b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.html b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.html new file mode 100644 index 000000000000..cd7458a8e5af --- /dev/null +++ b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.html @@ -0,0 +1,24 @@ +
    +

    mat-select

    + + Favorite Food + + None + + {{food.viewValue}} + + + +

    Selected: {{foodControl.value}}

    +

    Native select

    + + Favorite Car + + +

    Selected: {{carControl.value}}

    +
    diff --git a/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.ts b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.ts new file mode 100644 index 000000000000..249d28c2a8af --- /dev/null +++ b/src/components-examples/material/select/select-reactive-form/select-reactive-form-example.ts @@ -0,0 +1,43 @@ +import {Component} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; + +interface Food { + value: string; + viewValue: string; +} + +interface Car { + value: string; + viewValue: string; +} + +/** + * @title Select in a reactive form + */ +@Component({ + selector: 'select-reactive-form-example', + templateUrl: 'select-reactive-form-example.html', + styleUrls: ['select-reactive-form-example.css'], +}) +export class SelectReactiveFormExample { + form: FormGroup; + foods: Food[] = [ + {value: 'steak-0', viewValue: 'Steak'}, + {value: 'pizza-1', viewValue: 'Pizza'}, + {value: 'tacos-2', viewValue: 'Tacos'} + ]; + cars: Car[] = [ + {value: 'volvo', viewValue: 'Volvo'}, + {value: 'saab', viewValue: 'Saab'}, + {value: 'mercedes', viewValue: 'Mercedes'} + ]; + foodControl = new FormControl(this.foods[2].value); + carControl = new FormControl(this.cars[1].value); + + constructor() { + this.form = new FormGroup({ + food: this.foodControl, + car: this.carControl + }); + } +} diff --git a/src/components-examples/material/sidenav/sidenav-fixed/sidenav-fixed-example.html b/src/components-examples/material/sidenav/sidenav-fixed/sidenav-fixed-example.html index dcc43afcd567..d43ed7fe251a 100644 --- a/src/components-examples/material/sidenav/sidenav-fixed/sidenav-fixed-example.html +++ b/src/components-examples/material/sidenav/sidenav-fixed/sidenav-fixed-example.html @@ -11,10 +11,12 @@

    Fixed

    - + Top gap +

    - + Bottom gap +

    @@ -23,4 +25,4 @@ Footer -
    Please open on Stackblitz to see result
    +
    Please open on StackBlitz to see result
    diff --git a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.css b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.css index 1f954ba8b48a..60f6e261b7ef 100644 --- a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.css +++ b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.css @@ -1,5 +1,5 @@ .example-h2 { - margin: 10px; + margin: 0 8px 16px; } .example-section { @@ -10,7 +10,7 @@ } .example-margin { - margin: 10px; + margin: 8px; } .mat-slider-horizontal { @@ -20,3 +20,11 @@ .mat-slider-vertical { height: 300px; } + +.mat-card + .mat-card { + margin-top: 8px; +} + +.example-result-card h2 { + margin: 0 8px; +} diff --git a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html index 382b5e39274b..0cbbcd2ec906 100644 --- a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html +++ b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html @@ -4,16 +4,20 @@

    Slider configuration

    - + Value + - + Min value + - + Max value + - + Step size +
    @@ -23,7 +27,8 @@

    Slider configuration

    Auto ticks - + Tick interval + @@ -43,9 +48,9 @@

    Slider configuration

    - + -

    Result

    +

    Result

    - diff --git a/src/components-examples/material/snack-bar/snack-bar-overview/snack-bar-overview-example.html b/src/components-examples/material/snack-bar/snack-bar-overview/snack-bar-overview-example.html index 67065299a718..bc311ebcf18a 100644 --- a/src/components-examples/material/snack-bar/snack-bar-overview/snack-bar-overview-example.html +++ b/src/components-examples/material/snack-bar/snack-bar-overview/snack-bar-overview-example.html @@ -1,9 +1,11 @@ - + Message + - + Action + - + diff --git a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.css b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.css index ece1d0db513c..93993f80f01d 100644 --- a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.css +++ b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.css @@ -1,3 +1,3 @@ -mat-form-field { - margin-right: 12px; +.mat-form-field { + margin-right: 8px; } diff --git a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.html b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.html index ddfe4aa6fb3f..2329a7ddd860 100644 --- a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.html +++ b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.html @@ -1,5 +1,6 @@ - + Horizontal position + Start Center End @@ -8,12 +9,13 @@ - + Vertical position + Top Bottom - diff --git a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts index c3faff231b6e..dca9b8b0f7a9 100644 --- a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts +++ b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts @@ -14,14 +14,13 @@ import { styleUrls: ['snack-bar-position-example.css'], }) export class SnackBarPositionExample { - horizontalPosition: MatSnackBarHorizontalPosition = 'start'; verticalPosition: MatSnackBarVerticalPosition = 'bottom'; constructor(private _snackBar: MatSnackBar) {} openSnackBar() { - this._snackBar.open('Canonball!!', 'End now', { + this._snackBar.open('Cannonball!!', 'End now', { duration: 500, horizontalPosition: this.horizontalPosition, verticalPosition: this.verticalPosition, diff --git a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.css b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.css index 7432308753e6..fe5959ebc922 100644 --- a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.css +++ b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.css @@ -1 +1,7 @@ -/** No CSS for this example */ +.mat-stepper-horizontal { + margin-top: 8px; +} + +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html index e9162fea4e04..3d11b063e5c2 100644 --- a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html +++ b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html @@ -7,7 +7,8 @@
    Fill out your name - + Name +
    @@ -18,7 +19,9 @@ Fill out your address - + Address +
    @@ -28,7 +31,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.css b/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.css index e69de29bb2d1..f7c687a8ef7c 100644 --- a/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.css +++ b/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.css @@ -0,0 +1,3 @@ +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.html b/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.html index dbd2eaabf6b1..910198d9fbe9 100644 --- a/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.html +++ b/src/components-examples/material/stepper/stepper-errors/stepper-errors-example.html @@ -3,6 +3,7 @@ Fill out your name + Name
    @@ -14,7 +15,9 @@ Fill out your address - + Address +
    @@ -24,7 +27,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.css b/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.css index 7432308753e6..f7c687a8ef7c 100644 --- a/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.css +++ b/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.css @@ -1 +1,3 @@ -/** No CSS for this example */ +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.html b/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.html index 931635a17ea8..8c2fdb184522 100644 --- a/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.html +++ b/src/components-examples/material/stepper/stepper-label-position-bottom/stepper-label-position-bottom-example.html @@ -1,33 +1,36 @@ - - - Fill out your name - - - -
    - -
    - -
    - -
    - Fill out your address - - - -
    - - -
    -
    -
    - - Done - You are now done. -
    - - -
    -
    + +
    + Fill out your name + + Name + + +
    + +
    +
    +
    + +
    + Fill out your address + + Address + + +
    + + +
    +
    +
    + + Done +

    You are now done.

    +
    + + +
    +
    diff --git a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.css b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.css index 7432308753e6..fe5959ebc922 100644 --- a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.css +++ b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.css @@ -1 +1,7 @@ -/** No CSS for this example */ +.mat-stepper-horizontal { + margin-top: 8px; +} + +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html index 31c390bf4435..f4030d409739 100644 --- a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html +++ b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html @@ -7,6 +7,7 @@
    Fill out your name + Name
    @@ -18,7 +19,9 @@ Fill out your address - + Address +
    @@ -28,7 +31,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.css b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.css index 7432308753e6..fe5959ebc922 100644 --- a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.css +++ b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.css @@ -1 +1,7 @@ -/** No CSS for this example */ +.mat-stepper-horizontal { + margin-top: 8px; +} + +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html index bf1d75852a32..de89f8c16185 100644 --- a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html +++ b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html @@ -6,6 +6,7 @@ Fill out your name + Name
    @@ -17,7 +18,9 @@ Fill out your address - + Address +
    @@ -27,7 +30,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/stepper/stepper-states/stepper-states-example.css b/src/components-examples/material/stepper/stepper-states/stepper-states-example.css index e69de29bb2d1..fe5959ebc922 100644 --- a/src/components-examples/material/stepper/stepper-states/stepper-states-example.css +++ b/src/components-examples/material/stepper/stepper-states/stepper-states-example.css @@ -0,0 +1,7 @@ +.mat-stepper-horizontal { + margin-top: 8px; +} + +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-states/stepper-states-example.html b/src/components-examples/material/stepper/stepper-states/stepper-states-example.html index 657fd974f4de..487506522625 100644 --- a/src/components-examples/material/stepper/stepper-states/stepper-states-example.html +++ b/src/components-examples/material/stepper/stepper-states/stepper-states-example.html @@ -3,6 +3,7 @@ Fill out your name + Name
    @@ -14,7 +15,9 @@ Fill out your address - + Address +
    @@ -24,7 +27,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.css b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.css index 7432308753e6..1f1711124fd5 100644 --- a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.css +++ b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.css @@ -1 +1,7 @@ -/** No CSS for this example */ +.mat-stepper-vertical { + margin-top: 8px; +} + +.mat-form-field { + margin-top: 16px; +} diff --git a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html index 422e28d8cddf..10c0ca9bc29a 100644 --- a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html +++ b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html @@ -6,6 +6,7 @@ Fill out your name + Name
    @@ -17,7 +18,9 @@ Fill out your address - + Address +
    @@ -27,7 +30,7 @@ Done - You are now done. +

    You are now done.

    diff --git a/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.css b/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.css index 3353872e7fbe..a3cc2d66194c 100644 --- a/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.css +++ b/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.css @@ -7,7 +7,7 @@ tr.example-detail-row { } tr.example-element-row:not(.example-expanded-row):hover { - background: #777; + background: whitesmoke; } tr.example-element-row:not(.example-expanded-row):active { diff --git a/src/components-examples/material/table/table-filtering/table-filtering-example.html b/src/components-examples/material/table/table-filtering/table-filtering-example.html index f0e4e00af65f..d3f566eca289 100644 --- a/src/components-examples/material/table/table-filtering/table-filtering-example.html +++ b/src/components-examples/material/table/table-filtering/table-filtering-example.html @@ -1,5 +1,6 @@ - + Filter + diff --git a/src/components-examples/material/table/table-filtering/table-filtering-example.ts b/src/components-examples/material/table/table-filtering/table-filtering-example.ts index bb84986360af..f24acf8cdd1f 100644 --- a/src/components-examples/material/table/table-filtering/table-filtering-example.ts +++ b/src/components-examples/material/table/table-filtering/table-filtering-example.ts @@ -33,7 +33,8 @@ export class TableFilteringExample { displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; dataSource = new MatTableDataSource(ELEMENT_DATA); - applyFilter(filterValue: string) { + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; this.dataSource.filter = filterValue.trim().toLowerCase(); } } diff --git a/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.ts b/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.ts index 87c17befa8fe..89add3ca685e 100644 --- a/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.ts +++ b/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.ts @@ -1,11 +1,6 @@ import {Component} from '@angular/core'; import {Transaction} from '../table-footer-row/table-footer-row-example'; -export interface Transaction { - item: string; - cost: number; -} - /** * @title Table with multiple header and footer rows */ diff --git a/src/components-examples/material/table/table-overview/table-overview-example.html b/src/components-examples/material/table/table-overview/table-overview-example.html index 20620a8ef313..e94ece60f58a 100644 --- a/src/components-examples/material/table/table-overview/table-overview-example.html +++ b/src/components-examples/material/table/table-overview/table-overview-example.html @@ -1,5 +1,6 @@ - + Filter +
    diff --git a/src/components-examples/material/table/table-overview/table-overview-example.ts b/src/components-examples/material/table/table-overview/table-overview-example.ts index 1ef9ab996ebe..beb6b2a6f35b 100644 --- a/src/components-examples/material/table/table-overview/table-overview-example.ts +++ b/src/components-examples/material/table/table-overview/table-overview-example.ts @@ -48,7 +48,8 @@ export class TableOverviewExample implements OnInit { this.dataSource.sort = this.sort; } - applyFilter(filterValue: string) { + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; this.dataSource.filter = filterValue.trim().toLowerCase(); if (this.dataSource.paginator) { diff --git a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html index d28c2a90a6a4..a4f04d26946b 100644 --- a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html +++ b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html @@ -1,9 +1,7 @@ -
    - Selected tab index: - - - -
    + + Selected tab index + +
    - + New item... + diff --git a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts index fd610f208f1a..6f3ad3502760 100644 --- a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts +++ b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts @@ -4,7 +4,7 @@ import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree'; /** * Food data with nested structure. - * Each node has a name and an optiona list of children. + * Each node has a name and an optional list of children. */ interface FoodNode { name: string; @@ -26,7 +26,7 @@ const TREE_DATA: FoodNode[] = [ name: 'Green', children: [ {name: 'Broccoli'}, - {name: 'Brussel sprouts'}, + {name: 'Brussels sprouts'}, ] }, { name: 'Orange', diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts index 2e8fb28de4d7..771d1409c698 100644 --- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts +++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts @@ -4,7 +4,7 @@ import {MatTreeNestedDataSource} from '@angular/material/tree'; /** * Food data with nested structure. - * Each node has a name and an optiona list of children. + * Each node has a name and an optional list of children. */ interface FoodNode { name: string; @@ -26,7 +26,7 @@ const TREE_DATA: FoodNode[] = [ name: 'Green', children: [ {name: 'Broccoli'}, - {name: 'Brussel sprouts'}, + {name: 'Brussels sprouts'}, ] }, { name: 'Orange', diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index b392d425ed2a..c92f5b73fc33 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -2,12 +2,8 @@ package(default_visibility = ["//visibility:public"]) load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web") load("@build_bazel_rules_nodejs//internal/common:devmode_js_sources.bzl", "devmode_js_sources") -load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS") -load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_ENTRYPOINTS") -load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS") -load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_ENTRYPOINTS") +load("//tools:create-system-config.bzl", "create_system_config") load("//tools:defaults.bzl", "ng_module", "sass_binary") -load("//tools/bazel:expand_template.bzl", "expand_template") load("//tools/dev-server:index.bzl", "dev_server") ng_module( @@ -50,6 +46,7 @@ ng_module( "//src/dev-app/mdc-card", "//src/dev-app/mdc-checkbox", "//src/dev-app/mdc-chips", + "//src/dev-app/mdc-input", "//src/dev-app/mdc-menu", "//src/dev-app/mdc-progress-bar", "//src/dev-app/mdc-radio", @@ -102,17 +99,9 @@ sass_binary( ], ) -expand_template( +create_system_config( name = "system-config", - configuration_env_vars = ["angular_ivy_enabled"], output_name = "system-config.js", - substitutions = { - "$CDK_ENTRYPOINTS_TMPL": str(CDK_ENTRYPOINTS), - "$CDK_EXPERIMENTAL_ENTRYPOINTS_TMPL": str(CDK_EXPERIMENTAL_ENTRYPOINTS), - "$MATERIAL_ENTRYPOINTS_TMPL": str(MATERIAL_ENTRYPOINTS), - "$MATERIAL_EXPERIMENTAL_ENTRYPOINTS_TMPL": str(MATERIAL_EXPERIMENTAL_ENTRYPOINTS), - }, - template = "system-config-tmpl.js", ) # File group for all static files which are needed to serve the dev-app. These files are @@ -123,10 +112,10 @@ filegroup( srcs = [ "favicon.ico", "index.html", - "system-rxjs-operators.js", ":system-config", ":theme", "//src/dev-app/icon:icon_demo_assets", + "//tools:system-rxjs-operators.js", "@npm//:node_modules/@material/animation/dist/mdc.animation.js", "@npm//:node_modules/@material/auto-init/dist/mdc.autoInit.js", "@npm//:node_modules/@material/base/dist/mdc.base.js", @@ -138,7 +127,6 @@ filegroup( "@npm//:node_modules/@material/drawer/dist/mdc.drawer.js", "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", "@npm//:node_modules/@material/form-field/dist/mdc.formField.js", - "@npm//:node_modules/@material/grid-list/dist/mdc.gridList.js", "@npm//:node_modules/@material/icon-button/dist/mdc.iconButton.js", "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", "@npm//:node_modules/@material/linear-progress/dist/mdc.linearProgress.js", diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index ce74df90bbb6..192858a501e5 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -73,6 +73,7 @@ export class DevAppLayout { {name: 'MDC Card', route: '/mdc-card'}, {name: 'MDC Checkbox', route: '/mdc-checkbox'}, {name: 'MDC Chips', route: '/mdc-chips'}, + {name: 'MDC Input', route: '/mdc-input'}, {name: 'MDC Menu', route: '/mdc-menu'}, {name: 'MDC Radio', route: '/mdc-radio'}, {name: 'MDC Progress Bar', route: '/mdc-progress-bar'}, diff --git a/src/dev-app/dev-app/routes.ts b/src/dev-app/dev-app/routes.ts index 3d5a29bda459..09d2a91e3c29 100644 --- a/src/dev-app/dev-app/routes.ts +++ b/src/dev-app/dev-app/routes.ts @@ -63,6 +63,7 @@ export const DEV_APP_ROUTES: Routes = [ loadChildren: 'mdc-progress-bar/mdc-progress-bar-demo-module#MdcProgressBarDemoModule' }, {path: 'mdc-chips', loadChildren: 'mdc-chips/mdc-chips-demo-module#MdcChipsDemoModule'}, + {path: 'mdc-input', loadChildren: 'mdc-input/mdc-input-demo-module#MdcInputDemoModule'}, {path: 'mdc-menu', loadChildren: 'mdc-menu/mdc-menu-demo-module#MdcMenuDemoModule'}, {path: 'mdc-radio', loadChildren: 'mdc-radio/mdc-radio-demo-module#MdcRadioDemoModule'}, { diff --git a/src/dev-app/focus-trap/BUILD.bazel b/src/dev-app/focus-trap/BUILD.bazel index cf493c03c5c8..a8bd9adbc832 100644 --- a/src/dev-app/focus-trap/BUILD.bazel +++ b/src/dev-app/focus-trap/BUILD.bazel @@ -13,6 +13,7 @@ ng_module( ], deps = [ "//src/cdk/a11y", + "//src/cdk/platform", "//src/material/button", "//src/material/card", "//src/material/dialog", diff --git a/src/dev-app/focus-trap/focus-trap-demo.html b/src/dev-app/focus-trap/focus-trap-demo.html index 04bec6ebf188..b4f488d6c79c 100644 --- a/src/dev-app/focus-trap/focus-trap-demo.html +++ b/src/dev-app/focus-trap/focus-trap-demo.html @@ -5,8 +5,11 @@ -
    +
    @@ -19,16 +22,20 @@ -
    +
    -
    +
    @@ -42,8 +49,10 @@ -
    +
    @@ -56,21 +65,26 @@ Shadow DOMs - - -
    + + + +
    + + + + + +
    - + - - -
    - - - + + Shadow DOM not supported
    @@ -80,8 +94,10 @@ -
    +
    @@ -98,8 +114,10 @@ -
    +

    + +
    +

    Single Selection list

    + + +

    Favorite Grocery

    + + Bananas + Oranges + Apples + Strawberries +
    + +

    Selected: {{favoriteOptions | json}}

    +
    diff --git a/src/dev-app/list/list-demo.ts b/src/dev-app/list/list-demo.ts index 84a6eaf0a37b..9c1ec2908019 100644 --- a/src/dev-app/list/list-demo.ts +++ b/src/dev-app/list/list-demo.ts @@ -70,6 +70,8 @@ export class ListDemo { this.modelChangeEventCount++; } + favoriteOptions: string[] = []; + alertItem(msg: string) { alert(msg); } diff --git a/src/dev-app/mdc-chips/mdc-chips-demo.html b/src/dev-app/mdc-chips/mdc-chips-demo.html index 48bc859f7cfc..44e855163d5f 100644 --- a/src/dev-app/mdc-chips/mdc-chips-demo.html +++ b/src/dev-app/mdc-chips/mdc-chips-demo.html @@ -184,7 +184,7 @@

    Stacked

    You can also stack the chips if you want them on top of each other.

    - + {{aColor.name}} diff --git a/src/dev-app/mdc-input/BUILD.bazel b/src/dev-app/mdc-input/BUILD.bazel new file mode 100644 index 000000000000..482cb3149b89 --- /dev/null +++ b/src/dev-app/mdc-input/BUILD.bazel @@ -0,0 +1,35 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module", "sass_binary") + +ng_module( + name = "mdc-input", + srcs = glob(["**/*.ts"]), + assets = [ + ":mdc_input_demo_scss", + "mdc-input-demo.html", + ], + deps = [ + "//src/components-examples/material-experimental/mdc-form-field", + "//src/material-experimental/mdc-form-field", + "//src/material-experimental/mdc-input", + "//src/material/autocomplete", + "//src/material/button", + "//src/material/button-toggle", + "//src/material/card", + "//src/material/checkbox", + "//src/material/icon", + "//src/material/tabs", + "//src/material/toolbar", + "@npm//@angular/forms", + "@npm//@angular/router", + ], +) + +sass_binary( + name = "mdc_input_demo_scss", + src = "mdc-input-demo.scss", + deps = [ + "//src/cdk/text-field:text_field_scss_lib", + ], +) diff --git a/src/dev-app/mdc-input/mdc-input-demo-module.ts b/src/dev-app/mdc-input/mdc-input-demo-module.ts new file mode 100644 index 000000000000..6914619f39a2 --- /dev/null +++ b/src/dev-app/mdc-input/mdc-input-demo-module.ts @@ -0,0 +1,48 @@ +/** + * @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 {CommonModule} from '@angular/common'; +import { + MdcFormFieldExamplesModule +} from '@angular/components-examples/material-experimental/mdc-form-field'; +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatAutocompleteModule} from '@angular/material/autocomplete'; +import {MatButtonModule} from '@angular/material/button'; +import {MatButtonToggleModule} from '@angular/material/button-toggle'; +import {MatCardModule} from '@angular/material/card'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatIconModule} from '@angular/material/icon'; +import {MatTabsModule} from '@angular/material/tabs'; +import {MatToolbarModule} from '@angular/material/toolbar'; +import {RouterModule} from '@angular/router'; +import {MdcInputDemo} from './mdc-input-demo'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + MatAutocompleteModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatTabsModule, + MatToolbarModule, + MdcFormFieldExamplesModule, + ReactiveFormsModule, + RouterModule.forChild([{path: '', component: MdcInputDemo}]), + ], + declarations: [MdcInputDemo], +}) +export class MdcInputDemoModule {} diff --git a/src/dev-app/mdc-input/mdc-input-demo.html b/src/dev-app/mdc-input/mdc-input-demo.html new file mode 100644 index 000000000000..287aa6a46d13 --- /dev/null +++ b/src/dev-app/mdc-input/mdc-input-demo.html @@ -0,0 +1,672 @@ + + Basic + + + + Company (disabled) + + + +
    + + +
    + + + + + + Long last name that will be truncated + + +
    +

    + + Address + + + + Address 2 + + +

    + + + + +
    + + City + + + + + State + + + + + Postal code + + + mode_edit + {{postalCode.value.length}} / 5 + + +
    + + + + + + Error messages + +

    Regular

    + +

    + + Example + + This field is required + + + + Email + + + This field is required + + + Please enter a valid email address + + +

    + +

    With hint

    + + + Example + + This field is required + Please type something here + + + +
    +

    Inside a form

    + + + Example + + This field is required + + + +
    + +

    With a custom error function

    + + Example + + This field is required + + +
    +
    + + + Prefix + Suffix + +

    Text

    + + Amount + + + .00 + + +

    Icons

    + + Amount + + calendar_today + mode_edit + + +

    Icon buttons

    + + Amount + + + + + +

    + + Fill + Outline + +

    +
    +
    + + + Divider Colors + +

    Input

    + + Default color + + + + Accent color + + + + Warn color + + + +

    Textarea

    + + Default color + + + + Accent color + + + + Warn color + + + +

    With error

    + + Default color + + This field is required + + + Accent color + + This field is required + + + Warn color + + This field is required + +
    +
    + + + Hints + +

    Input

    +

    + + Character count (100 max) + + + {{characterCountInputHintExample.value.length}} / 100 + + +

    + +

    Textarea

    +

    + + Character count (100 max) + + + {{characterCountTextareaHintExample.value.length}} / 100 + + +

    +
    +
    + + + + + Hello  + + First name + + , + how are you? + + + +

    + + Disabled field + + + + Required field + + +

    +

    + + 100% width label + + +

    +

    + + Character count (100 max) + + {{input.value.length}} / 100 + +

    +

    + + Show hint label + + +

    + +

    + + + + I favorite bold label + + + I also home italic hint labels + + +

    +

    + + Show hint label with character count + + {{hintLabelWithCharCount.value.length}} + +

    +

    + + Enter text to count + + + Enter some text to count how many characters are in it. The character count is shown on + the right. + + + Length: {{longHint.value.length}} + + +

    +

    + Check to change the color: + + + +

    +

    + Check to make required: + + {{requiredField ? 'Required field' : 'Not required field'}} + + +

    +

    + Check to hide the required marker: + + + {{hideRequiredMarker ? 'Required Without Marker' : 'Required With Marker'}} + + + +

    +

    + + Auto Float + Always Float + +

    + +

    + + Label + + +

    + +

    + + What is your favorite color? + + +

    + +

    + + Prefixed + +

    Example: 
    + + + Suffixed + + .00 € + +
    + Both: + + Email address + + email  +  @gmail.com + +

    + +

    + Empty: + +

    +
    +
    + + + Number Inputs + + + + + + + + + + + + +
    Table + + + + +
    {{i+1}} + + Value + + + + + {{item.value}}
    +
    +
    + + + Forms + + + Reactive + + + + Template + + + + +
    + + Delayed value + + +
    +
    +
    + + + + Floating labels + + +
    + + + + + + + + + + Only label + + + + + Label and placeholder + + + + + Always float + + + + + Label w/ placeholder + + +
    + + + + +
    +
    + + + Textarea Autosize + +

    Regular <textarea>

    + + +

    Regular <textarea> with maxRows and minRows

    +
    +   + +
    + + + +

    <textarea> with mat-form-field

    +
    + + Autosized textarea + + +
    + +

    <textarea> with ngModel

    +
    + +
    + + +

    <textarea> with bindable autosize

    + + Autosize enabled + + +
    +
    + + + Appearance + + + Fill appearance + + This field is required + Please type something here + + + + Outline appearance + + This field is required + Please type something here + + + + + +
    + + Fill appearance + + This field is required + Please type something here + + + + Outline appearance + + This field is required + Please type something here + +
    +
    +
    + + + Autofill + +
    + + Use custom autofill style + + + Autofill monitored + + + + is autofilled? {{isAutofilled ? 'yes' : 'no'}} +
    +
    +
    + + + Textarea form-fields + + + Label + + + + Label + + +

    + Disable textarea form-fields +

    +
    +
    + + + Appearance toggle + + + Label + + +

    + + Fill + Outline + +

    +
    +
    + + + Autocomplete + + + Pick Number + + + {{option}} + + + + + + + + Outline form field in a tab + + + + + Tab 1 input + + + + + + Tab 2 input + + + + + + + + + Baseline + + Shifted text + + Label + + + + + + + + Examples + +

    Custom control

    + +
    +
    diff --git a/src/dev-app/mdc-input/mdc-input-demo.scss b/src/dev-app/mdc-input/mdc-input-demo.scss new file mode 100644 index 000000000000..4e7a58d008ec --- /dev/null +++ b/src/dev-app/mdc-input/mdc-input-demo.scss @@ -0,0 +1,45 @@ +@import '../../cdk/text-field/text-field'; + +.demo-basic { + padding: 0; +} + +.demo-basic .mat-card-content { + padding: 16px; +} + +.demo-horizontal-spacing { + margin: 0 12px; +} + +.demo-full-width { + width: 100%; +} + +.demo-card { + margin: 16px; + + mat-card-content { + font-size: 16px; + } +} + +.demo-text-align-end { + text-align: end; +} + +.demo-textarea { + resize: none; + border: none; + overflow: auto; + padding: 0; + background: lightblue; +} + +.demo-custom-autofill-style { + @include cdk-text-field-autofill-color(transparent, red); +} + +.demo-rows { + width: 30px; +} diff --git a/src/dev-app/mdc-input/mdc-input-demo.ts b/src/dev-app/mdc-input/mdc-input-demo.ts new file mode 100644 index 000000000000..207ffb19942d --- /dev/null +++ b/src/dev-app/mdc-input/mdc-input-demo.ts @@ -0,0 +1,105 @@ +/** + * @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 {ChangeDetectionStrategy, Component} from '@angular/core'; +import {FormControl, Validators} from '@angular/forms'; +import { + FloatLabelType, + MatFormFieldAppearance +} from '@angular/material-experimental/mdc-form-field'; +import {ErrorStateMatcher} from '@angular/material/core'; + +let max = 5; + +const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'mdc-input-demo', + templateUrl: 'mdc-input-demo.html', + styleUrls: ['mdc-input-demo.css'], +}) +export class MdcInputDemo { + floatingLabel: FloatLabelType = 'auto'; + color: boolean; + requiredField: boolean; + disableTextarea: boolean; + hideRequiredMarker: boolean; + ctrlDisabled = false; + textareaNgModelValue: string; + textareaAutosizeEnabled = false; + appearance: MatFormFieldAppearance = 'fill'; + prefixSuffixAppearance: MatFormFieldAppearance = 'fill'; + placeholderTestControl = new FormControl('', Validators.required); + options: string[] = ['One', 'Two', 'Three']; + + name: string; + errorMessageExample1: string; + errorMessageExample2: string; + errorMessageExample3: string; + errorMessageExample4: string; + dividerColorExample1: string; + dividerColorExample2: string; + dividerColorExample3: string; + items: {value: number}[] = [ + {value: 10}, + {value: 20}, + {value: 30}, + {value: 40}, + {value: 50}, + ]; + rows = 8; + formControl = new FormControl('hello', Validators.required); + emailFormControl = new FormControl('', [Validators.required, Validators.pattern(EMAIL_REGEX)]); + delayedFormControl = new FormControl(''); + model = 'hello'; + isAutofilled = false; + customAutofillStyle = true; + + legacyAppearance: string; + standardAppearance: string; + fillAppearance: string; + outlineAppearance: string; + + constructor() { + setTimeout(() => this.delayedFormControl.setValue('hello'), 100); + } + + addABunch(n: number) { + for (let x = 0; x < n; x++) { + this.items.push({ value: ++max }); + } + } + + customErrorStateMatcher: ErrorStateMatcher = { + isErrorState: (control: FormControl | null) => { + if (control) { + const hasInteraction = control.dirty || control.touched; + const isInvalid = control.invalid; + + return !!(hasInteraction && isInvalid); + } + + return false; + } + }; + + togglePlaceholderTestValue() { + this.placeholderTestControl.setValue(this.placeholderTestControl.value === '' ? 'Value' : ''); + } + + togglePlaceholderTestTouched() { + this.placeholderTestControl.touched ? + this.placeholderTestControl.markAsUntouched() : + this.placeholderTestControl.markAsTouched(); + } + + parseNumber(value: string): number { + return Number(value); + } +} diff --git a/src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.html b/src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.html index 105158669f29..40bfb0fbbb73 100644 --- a/src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.html +++ b/src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.html @@ -8,6 +8,10 @@ Disabled Slide Toggle + + Always disabled (no value binding) + + Disable Bound diff --git a/src/dev-app/platform/platform-demo.html b/src/dev-app/platform/platform-demo.html index d6fe9dbc49e6..79397500200e 100644 --- a/src/dev-app/platform/platform-demo.html +++ b/src/dev-app/platform/platform-demo.html @@ -1,12 +1,13 @@ -

    Is Android: {{ platform.ANDROID }}

    -

    Is iOS: {{ platform.IOS }}

    -

    Is Firefox: {{ platform.FIREFOX }}

    -

    Is Blink: {{ platform.BLINK }}

    -

    Is Webkit: {{ platform.WEBKIT }}

    -

    Is Trident: {{ platform.TRIDENT }}

    -

    Is Edge: {{ platform.EDGE }}

    +

    Is Android: {{platform.ANDROID}}

    +

    Is iOS: {{platform.IOS}}

    +

    Is Firefox: {{platform.FIREFOX}}

    +

    Is Blink: {{platform.BLINK}}

    +

    Is Webkit: {{platform.WEBKIT}}

    +

    Is Trident: {{platform.TRIDENT}}

    +

    Is Edge: {{platform.EDGE}}

    +

    Is Safari: {{platform.SAFARI}}

    Supported input types: - {{ type }}, + {{type}},

    diff --git a/src/dev-app/popover-edit/popover-edit-demo.ts b/src/dev-app/popover-edit/popover-edit-demo.ts index 44d7a2bef9cb..0b4dd0adb229 100644 --- a/src/dev-app/popover-edit/popover-edit-demo.ts +++ b/src/dev-app/popover-edit/popover-edit-demo.ts @@ -10,25 +10,25 @@ import {Component} from '@angular/core'; @Component({ template: ` -

    CDK popover-edit with cdk-table

    +

    CDK popover-edit with cdk-table

    -

    CDK popover-edit with cdk-table flex

    +

    CDK popover-edit with cdk-table flex

    -

    CDK popover-edit with vanilla table

    +

    CDK popover-edit with vanilla table

    -

    CDK popover-edit with vanilla table and tab out

    +

    CDK popover-edit with vanilla table and tab out

    -

    CDK popover-edit with vanilla table

    +

    CDK popover-edit with vanilla table

    -

    Material popover-edit with mat-table and cell span

    +

    Material popover-edit with mat-table and cell span

    -

    Material popover-edit with mat-table

    +

    Material popover-edit with mat-table

    -

    Material popover-edit with mat-table flex

    +

    Material popover-edit with mat-table flex

    -

    Material popover-edit with mat

    +

    Material popover-edit with mat-table and tab out

    `, }) diff --git a/src/dev-app/select/select-demo.html b/src/dev-app/select/select-demo.html index abc808b618cd..3fa018edb5c1 100644 --- a/src/dev-app/select/select-demo.html +++ b/src/dev-app/select/select-demo.html @@ -398,7 +398,7 @@

    Error message with errorStateMatcher

    Starter pokemon - + {{ creature.viewValue }} diff --git a/src/dev-app/system-config-tmpl.js b/src/dev-app/system-config-tmpl.js deleted file mode 100644 index 4bccbd526fba..000000000000 --- a/src/dev-app/system-config-tmpl.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * @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 - */ - -// Note that this file isn't being transpiled so we need to keep it in ES5. Also -// identifiers of the format "$NAME_TMPL" will be replaced by the Bazel rule that -// converts this template file into the actual SystemJS configuration file. - -var CDK_PACKAGES = $CDK_ENTRYPOINTS_TMPL; -var CDK_EXPERIMENTAL_PACKAGES = $CDK_EXPERIMENTAL_ENTRYPOINTS_TMPL; -var MATERIAL_PACKAGES = $MATERIAL_ENTRYPOINTS_TMPL; -var MATERIAL_EXPERIMENTAL_PACKAGES = $MATERIAL_EXPERIMENTAL_ENTRYPOINTS_TMPL; - -/** Whether the dev-app is served with Ivy enabled. */ -var isRunningWithIvy = '$ANGULAR_IVY_ENABLED_TMPL'.toString() === 'True'; - -/** Bazel runfile path referring to the "src" folder of the project. */ -var srcRunfilePath = 'src'; - -/** Path mappings that will be registered in SystemJS. */ -var pathMapping = {}; - -/** Package configurations that will be used in SystemJS. */ -var packagesConfig = {}; - -// Configure all primary entry-points. -configureEntryPoint('cdk'); -configureEntryPoint('cdk-experimental'); -configureEntryPoint('components-examples'); -configureEntryPoint('material'); -configureEntryPoint('material-experimental'); -configureEntryPoint('material-moment-adapter'); - -// Configure all secondary entry-points. -CDK_PACKAGES.forEach(function(pkgName) { - configureEntryPoint('cdk', pkgName); -}); -CDK_EXPERIMENTAL_PACKAGES.forEach(function(pkgName) { - configureEntryPoint('cdk-experimental', pkgName); -}); -MATERIAL_EXPERIMENTAL_PACKAGES.forEach(function(pkgName) { - configureEntryPoint('material-experimental', pkgName); -}); -MATERIAL_PACKAGES.forEach(function(pkgName) { - configureEntryPoint('material', pkgName); -}); -configureEntryPoint('google-maps'); -configureEntryPoint('youtube-player'); - -/** Configures the specified package, its entry-point and its examples. */ -function configureEntryPoint(pkgName, entryPoint) { - var name = entryPoint ? pkgName + '/' + entryPoint : pkgName; - var examplesName = 'components-examples/' + name; - - pathMapping['@angular/' + name] = srcRunfilePath + '/' + name; - pathMapping['@angular/' + examplesName] = srcRunfilePath + '/' + examplesName; - - // Ensure that imports which resolve to the entry-point directory are - // redirected to the "index.js" file of the directory. - packagesConfig[srcRunfilePath + '/' + name] = - packagesConfig[srcRunfilePath + '/' + examplesName] = {main: 'index.js'}; -} - -/** - * Gets the path to the given bundle. Respects processing done by ngcc when - * running with Ivy enabled. - */ -function getBundlePath(bundleName, basePath) { - var relativeBundlePath = 'bundles/' + bundleName; - if (isRunningWithIvy) { - relativeBundlePath = '__ivy_ngcc__/' + relativeBundlePath; - } - return (basePath || '') + '/' + relativeBundlePath ; -} - -var map = Object.assign({ - // Maps imports where the AMD module names start with workspace name (commonly done in Bazel). - // This is needed for compatibility with dynamic runfile resolution of the devserver and the - // static runfile resolution done in the "pkg_web" rule. In the built web package, the output - // tree artifact serves as workspace root and root of the current dev-app Bazel package. - 'angular_material': '', - 'angular_material/src/dev-app': '', - - 'main': 'main.js', - 'tslib': 'tslib/tslib.js', - 'moment': 'moment/min/moment-with-locales.min.js', - - 'rxjs': 'rxjs/bundles/rxjs.umd.min.js', - 'rxjs/operators': 'system-rxjs-operators.js', - - // MDC Web - '@material/animation': '@material/animation/dist/mdc.animation.js', - '@material/auto-init': '@material/auto-init/dist/mdc.autoInit.js', - '@material/base': '@material/base/dist/mdc.base.js', - '@material/checkbox': '@material/checkbox/dist/mdc.checkbox.js', - '@material/chips': '@material/chips/dist/mdc.chips.js', - '@material/dialog': '@material/dialog/dist/mdc.dialog.js', - '@material/dom': '@material/dom/dist/mdc.dom.js', - '@material/drawer': '@material/drawer/dist/mdc.drawer.js', - '@material/floating-label': '@material/floating-label/dist/mdc.floatingLabel.js', - '@material/form-field': '@material/form-field/dist/mdc.formField.js', - '@material/grid-list': '@material/grid-list/dist/mdc.gridList.js', - '@material/icon-button': '@material/icon-button/dist/mdc.iconButton.js', - '@material/line-ripple': '@material/line-ripple/dist/mdc.lineRipple.js', - '@material/linear-progress': '@material/linear-progress/dist/mdc.linearProgress.js', - '@material/list': '@material/list/dist/mdc.list.js', - '@material/menu': '@material/menu/dist/mdc.menu.js', - '@material/menu-surface': '@material/menu-surface/dist/mdc.menuSurface.js', - '@material/notched-outline': '@material/notched-outline/dist/mdc.notchedOutline.js', - '@material/radio': '@material/radio/dist/mdc.radio.js', - '@material/ripple': '@material/ripple/dist/mdc.ripple.js', - '@material/select': '@material/select/dist/mdc.select.js', - '@material/slider': '@material/slider/dist/mdc.slider.js', - '@material/snackbar': '@material/snackbar/dist/mdc.snackbar.js', - '@material/switch': '@material/switch/dist/mdc.switch.js', - '@material/tab': '@material/tab/dist/mdc.tab.js', - '@material/tab-bar': '@material/tab-bar/dist/mdc.tabBar.js', - '@material/tab-indicator': '@material/tab-indicator/dist/mdc.tabIndicator.js', - '@material/tab-scroller': '@material/tab-scroller/dist/mdc.tabScroller.js', - '@material/text-field': '@material/textfield/dist/mdc.textfield.js', - '@material/top-app-bar': '@material/top-app-bar/dist/mdc.topAppBar.js' -}, pathMapping); - -var packages = Object.assign({ - // Set the default extension for the root package, because otherwise the dev-app can't - // be built within the production mode. Due to missing file extensions. - '.': {defaultExtension: 'js'}, - - // Angular specific mappings. - '@angular/core': {main: getBundlePath('core.umd.js')}, - '@angular/common': {main: getBundlePath('common.umd.js')}, - '@angular/common/http': {main: getBundlePath('common-http.umd.js', '../')}, - '@angular/compiler': {main: getBundlePath('compiler.umd.js')}, - '@angular/forms': {main: getBundlePath('forms.umd.js')}, - '@angular/animations': {main: getBundlePath('animations.umd.js')}, - '@angular/elements': {main: getBundlePath('elements.umd.js')}, - '@angular/router': {main: getBundlePath('router.umd.js')}, - '@angular/animations/browser': { - main: getBundlePath('animations-browser.umd.js', '../') - }, - '@angular/platform-browser/animations': { - main: getBundlePath('platform-browser-animations.umd.js', '../') - }, - '@angular/platform-browser': {main: getBundlePath('platform-browser.umd.js')}, - '@angular/platform-browser-dynamic': {main: getBundlePath('platform-browser-dynamic.umd.js')}, -}, packagesConfig); - -// Configure the base path and map the different node packages. -System.config({ - map: map, - packages: packages -}); diff --git a/src/e2e-app/radio/radio-e2e.html b/src/e2e-app/radio/radio-e2e.html index 28ba53e3541b..5b2acf7a317d 100644 --- a/src/e2e-app/radio/radio-e2e.html +++ b/src/e2e-app/radio/radio-e2e.html @@ -1,6 +1,6 @@
    Charmander diff --git a/src/google-maps/google-map/google-map.spec.ts b/src/google-maps/google-map/google-map.spec.ts index cce5991b4497..8a97d9b34333 100644 --- a/src/google-maps/google-map/google-map.spec.ts +++ b/src/google-maps/google-map/google-map.spec.ts @@ -99,6 +99,28 @@ describe('GoogleMap', () => { expect(container.nativeElement.style.width).toBe('350px'); }); + it('should be able to set a number value as the width/height', () => { + mapSpy = createMapSpy(DEFAULT_OPTIONS); + mapConstructorSpy = createMapConstructorSpy(mapSpy); + + const fixture = TestBed.createComponent(TestApp); + const instance = fixture.componentInstance; + instance.height = 750; + instance.width = 400; + fixture.detectChanges(); + + const container = fixture.debugElement.query(By.css('div'))!.nativeElement; + expect(container.style.height).toBe('750px'); + expect(container.style.width).toBe('400px'); + + instance.height = '500'; + instance.width = '600'; + fixture.detectChanges(); + + expect(container.style.height).toBe('500px'); + expect(container.style.width).toBe('600px'); + }); + it('sets center and zoom of the map', () => { const options = {center: {lat: 3, lng: 5}, zoom: 7}; mapSpy = createMapSpy(options); @@ -274,8 +296,8 @@ describe('GoogleMap', () => { }) class TestApp { @ViewChild(GoogleMap) map: GoogleMap; - height?: string; - width?: string; + height?: string | number; + width?: string | number; center?: google.maps.LatLngLiteral; zoom?: number; options?: google.maps.MapOptions; diff --git a/src/google-maps/google-map/google-map.ts b/src/google-maps/google-map/google-map.ts index 32b1d32e30fa..041bf2b80887 100644 --- a/src/google-maps/google-map/google-map.ts +++ b/src/google-maps/google-map/google-map.ts @@ -22,6 +22,7 @@ import { Optional, Inject, PLATFORM_ID, + NgZone, } from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; @@ -64,10 +65,7 @@ export const DEFAULT_WIDTH = '500px'; encapsulation: ViewEncapsulation.None, }) export class GoogleMap implements OnChanges, OnInit, OnDestroy { - private _eventManager = new MapEventManager(); - - /** Whether we're currently rendering inside a browser. */ - private _isBrowser: boolean; + private _eventManager: MapEventManager = new MapEventManager(this._ngZone); private _googleMapChanges: Observable; private readonly _options = new BehaviorSubject(DEFAULT_OPTIONS); @@ -78,9 +76,12 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { private _mapEl: HTMLElement; _googleMap: UpdatedGoogleMap; - @Input() height = DEFAULT_HEIGHT; + /** Whether we're currently rendering inside a browser. */ + _isBrowser: boolean; - @Input() width = DEFAULT_WIDTH; + @Input() height: string | number = DEFAULT_HEIGHT; + + @Input() width: string | number = DEFAULT_WIDTH; @Input() set center(center: google.maps.LatLngLiteral|google.maps.LatLng) { @@ -219,10 +220,11 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.zoom_changed */ - @Output() zoomChanged: Observable = this._eventManager.getLazyEmitter('zoomChanged'); + @Output() zoomChanged: Observable = this._eventManager.getLazyEmitter('zoom_changed'); constructor( private readonly _elementRef: ElementRef, + private _ngZone: NgZone, /** * @deprecated `platformId` parameter to become required. * @breaking-change 10.0.0 @@ -431,8 +433,9 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { private _setSize() { if (this._mapEl) { - this._mapEl.style.height = this.height || DEFAULT_HEIGHT; - this._mapEl.style.width = this.width || DEFAULT_WIDTH; + const styles = this._mapEl.style; + styles.height = coerceCssPixelValue(this.height) || DEFAULT_HEIGHT; + styles.width = coerceCssPixelValue(this.width) || DEFAULT_WIDTH; } } @@ -453,7 +456,12 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { Observable { return optionsChanges.pipe( take(1), - map(options => new google.maps.Map(this._mapEl, options)), + map(options => { + // Create the object outside the zone so its events don't trigger change detection. + // We'll bring it back in inside the `MapEventManager` only for the events that the + // user has subscribed to. + return this._ngZone.runOutsideAngular(() => new google.maps.Map(this._mapEl, options)); + }), shareReplay(1)); } @@ -493,3 +501,14 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { } } } + +const cssUnitsPattern = /([A-Za-z%]+)$/; + +/** Coerces a value to a CSS pixel value. */ +function coerceCssPixelValue(value: any): string { + if (value == null) { + return ''; + } + + return cssUnitsPattern.test(value) ? value : `${value}px`; +} diff --git a/src/google-maps/map-event-manager.ts b/src/google-maps/map-event-manager.ts index df5fc998eb65..8d84cf4d21df 100644 --- a/src/google-maps/map-event-manager.ts +++ b/src/google-maps/map-event-manager.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {NgZone} from '@angular/core'; import {Observable, Subscriber} from 'rxjs'; type MapEventManagerTarget = { @@ -28,6 +29,8 @@ export class MapEventManager { this._listeners = []; } + constructor(private _ngZone: NgZone) {} + /** Gets an observable that adds an event listener to the map when a consumer subscribes to it. */ getLazyEmitter(name: string): Observable { const observable = new Observable(observer => { @@ -37,7 +40,9 @@ export class MapEventManager { return undefined; } - const listener = this._target.addListener(name, (event: T) => observer.next(event)); + const listener = this._target.addListener(name, (event: T) => { + this._ngZone.run(() => observer.next(event)); + }); this._listeners.push(listener); return () => listener.remove(); }); diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index f84d293839f0..47a879ddca81 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -16,6 +16,7 @@ import { OnDestroy, OnInit, Output, + NgZone, } from '@angular/core'; import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; @@ -33,7 +34,7 @@ import {MapEventManager} from '../map-event-manager'; host: {'style': 'display: none'}, }) export class MapInfoWindow implements OnInit, OnDestroy { - private _eventManager = new MapEventManager(); + private _eventManager = new MapEventManager(this._ngZone); private readonly _options = new BehaviorSubject({}); private readonly _position = new BehaviorSubject(undefined); @@ -87,17 +88,26 @@ export class MapInfoWindow implements OnInit, OnDestroy { zindexChanged: Observable = this._eventManager.getLazyEmitter('zindex_changed'); constructor(private readonly _googleMap: GoogleMap, - private _elementRef: ElementRef) {} + private _elementRef: ElementRef, + private _ngZone: NgZone) {} ngOnInit() { - this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => { - if (this._infoWindow) { - this._infoWindow.setOptions(options); - } else { - this._infoWindow = new google.maps.InfoWindow(options); - this._eventManager.setTarget(this._infoWindow); - } - }); + if (this._googleMap._isBrowser) { + this._combineOptions().pipe(takeUntil(this._destroy)).subscribe(options => { + if (this._infoWindow) { + this._infoWindow.setOptions(options); + } else { + // Create the object outside the zone so its events don't trigger change detection. + // We'll bring it back in inside the `MapEventManager` only for the events that the + // user has subscribed to. + this._ngZone.runOutsideAngular(() => { + this._infoWindow = new google.maps.InfoWindow(options); + }); + + this._eventManager.setTarget(this._infoWindow); + } + }); + } } ngOnDestroy() { @@ -147,7 +157,7 @@ export class MapInfoWindow implements OnInit, OnDestroy { */ open(anchor?: MapMarker) { const marker = anchor ? anchor._marker : undefined; - if (this._googleMap._googleMap) { + if (this._googleMap._googleMap && this._infoWindow) { this._elementRef.nativeElement.style.display = ''; this._infoWindow!.open(this._googleMap._googleMap, marker); } diff --git a/src/google-maps/map-marker/map-marker.ts b/src/google-maps/map-marker/map-marker.ts index ee2b5f040e30..c13a05a65b7d 100644 --- a/src/google-maps/map-marker/map-marker.ts +++ b/src/google-maps/map-marker/map-marker.ts @@ -16,7 +16,8 @@ import { OnDestroy, OnInit, Output, - ViewEncapsulation + ViewEncapsulation, + NgZone } from '@angular/core'; import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, take, takeUntil} from 'rxjs/operators'; @@ -43,7 +44,7 @@ export const DEFAULT_MARKER_OPTIONS = { encapsulation: ViewEncapsulation.None, }) export class MapMarker implements OnInit, OnDestroy { - private _eventManager = new MapEventManager(); + private _eventManager = new MapEventManager(this._ngZone); private readonly _options = new BehaviorSubject(DEFAULT_MARKER_OPTIONS); private readonly _title = new BehaviorSubject(undefined); @@ -236,22 +237,29 @@ export class MapMarker implements OnInit, OnDestroy { _marker?: google.maps.Marker; - constructor(private readonly _googleMap: GoogleMap) {} + constructor( + private readonly _googleMap: GoogleMap, + private _ngZone: NgZone) {} ngOnInit() { - const combinedOptionsChanges = this._combineOptions(); - - combinedOptionsChanges.pipe(take(1)).subscribe(options => { - this._marker = new google.maps.Marker(options); - this._marker.setMap(this._googleMap._googleMap); - this._eventManager.setTarget(this._marker); - }); - - this._watchForOptionsChanges(); - this._watchForTitleChanges(); - this._watchForPositionChanges(); - this._watchForLabelChanges(); - this._watchForClickableChanges(); + if (this._googleMap._isBrowser) { + const combinedOptionsChanges = this._combineOptions(); + + combinedOptionsChanges.pipe(take(1)).subscribe(options => { + // Create the object outside the zone so its events don't trigger change detection. + // We'll bring it back in inside the `MapEventManager` only for the events that the + // user has subscribed to. + this._ngZone.runOutsideAngular(() => this._marker = new google.maps.Marker(options)); + this._marker!.setMap(this._googleMap._googleMap); + this._eventManager.setTarget(this._marker); + }); + + this._watchForOptionsChanges(); + this._watchForTitleChanges(); + this._watchForPositionChanges(); + this._watchForLabelChanges(); + this._watchForClickableChanges(); + } } ngOnDestroy() { diff --git a/src/google-maps/map-polyline/map-polyline.ts b/src/google-maps/map-polyline/map-polyline.ts index 8dee190a2c47..e815c253dfb5 100644 --- a/src/google-maps/map-polyline/map-polyline.ts +++ b/src/google-maps/map-polyline/map-polyline.ts @@ -15,6 +15,7 @@ import { OnDestroy, OnInit, Output, + NgZone, } from '@angular/core'; import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; import {map, take, takeUntil} from 'rxjs/operators'; @@ -30,7 +31,7 @@ import {MapEventManager} from '../map-event-manager'; selector: 'map-polyline', }) export class MapPolyline implements OnInit, OnDestroy { - private _eventManager = new MapEventManager(); + private _eventManager = new MapEventManager(this._ngZone); private readonly _options = new BehaviorSubject({}); private readonly _path = new BehaviorSubject|google.maps.LatLng[]| @@ -129,19 +130,26 @@ export class MapPolyline implements OnInit, OnDestroy { polylineRightclick: Observable = this._eventManager.getLazyEmitter('rightclick'); - constructor(private readonly _map: GoogleMap) {} + constructor( + private readonly _map: GoogleMap, + private _ngZone: NgZone) {} ngOnInit() { - const combinedOptionsChanges = this._combineOptions(); - - combinedOptionsChanges.pipe(take(1)).subscribe(options => { - this._polyline = new google.maps.Polyline(options); - this._polyline.setMap(this._map._googleMap); - this._eventManager.setTarget(this._polyline); - }); - - this._watchForOptionsChanges(); - this._watchForPathChanges(); + if (this._map._isBrowser) { + const combinedOptionsChanges = this._combineOptions(); + + combinedOptionsChanges.pipe(take(1)).subscribe(options => { + // Create the object outside the zone so its events don't trigger change detection. + // We'll bring it back in inside the `MapEventManager` only for the events that the + // user has subscribed to. + this._ngZone.runOutsideAngular(() => this._polyline = new google.maps.Polyline(options)); + this._polyline.setMap(this._map._googleMap); + this._eventManager.setTarget(this._polyline); + }); + + this._watchForOptionsChanges(); + this._watchForPathChanges(); + } } ngOnDestroy() { @@ -151,7 +159,9 @@ export class MapPolyline implements OnInit, OnDestroy { for (let listener of this._listeners) { listener.remove(); } - this._polyline.setMap(null); + if (this._polyline) { + this._polyline.setMap(null); + } } /** diff --git a/src/material-experimental/BUILD.bazel b/src/material-experimental/BUILD.bazel index 0e6954ed8669..05ae90cf30c2 100644 --- a/src/material-experimental/BUILD.bazel +++ b/src/material-experimental/BUILD.bazel @@ -23,7 +23,11 @@ ts_library( ng_package( name = "npm_package", srcs = ["package.json"], - data = MATERIAL_EXPERIMENTAL_SCSS_LIBS, + data = MATERIAL_EXPERIMENTAL_SCSS_LIBS + [ + "//src/material-experimental/mdc-helpers", + "//src/material-experimental/mdc-theming", + "//src/material-experimental/mdc-typography", + ], entry_point = ":public-api.ts", tags = ["release-package"], deps = MATERIAL_EXPERIMENTAL_TARGETS + MATERIAL_EXPERIMENTAL_TESTING_TARGETS, diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index 3dd94266f45c..8467738ff3c3 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -7,19 +7,25 @@ entryPoints = [ "mdc-checkbox/testing", "mdc-chips", "mdc-chips/testing", - "mdc-helpers", + "mdc-form-field", + "mdc-form-field/testing", + "mdc-input", + "mdc-input/testing", "mdc-list", "mdc-menu", "mdc-menu/testing", "mdc-progress-bar", + "mdc-progress-bar/testing", "mdc-radio", "mdc-select", "mdc-sidenav", "mdc-slide-toggle", "mdc-slide-toggle/testing", "mdc-slider", - "mdc-tabs", + "mdc-slider/testing", + "mdc-snackbar", "mdc-table", + "mdc-tabs", "popover-edit", ] diff --git a/src/material-experimental/mdc-autocomplete/module.ts b/src/material-experimental/mdc-autocomplete/module.ts index 37655c4efb8f..546c290c4685 100644 --- a/src/material-experimental/mdc-autocomplete/module.ts +++ b/src/material-experimental/mdc-autocomplete/module.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule], exports: [MatCommonModule], }) export class MatAutocompleteModule { diff --git a/src/material-experimental/mdc-button/_mdc-button.scss b/src/material-experimental/mdc-button/_mdc-button.scss index 8afc49a17486..2e8b1a0b8dad 100644 --- a/src/material-experimental/mdc-button/_mdc-button.scss +++ b/src/material-experimental/mdc-button/_mdc-button.scss @@ -1,8 +1,8 @@ -@import '@material/button/mixins'; -@import '@material/button/variables'; -@import '@material/fab/mixins'; -@import '@material/ripple/mixins'; -@import '@material/icon-button/mixins'; +@import '@material/button/mixins.import'; +@import '@material/button/variables.import'; +@import '@material/fab/mixins.import'; +@import '@material/ripple/mixins.import'; +@import '@material/icon-button/mixins.import'; @import '../../material/core/ripple/ripple'; @import '../mdc-helpers/mdc-helpers'; diff --git a/src/material-experimental/mdc-button/button-base.ts b/src/material-experimental/mdc-button/button-base.ts index d428abb741fd..2b16d20e53b9 100644 --- a/src/material-experimental/mdc-button/button-base.ts +++ b/src/material-experimental/mdc-button/button-base.ts @@ -6,16 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ +import {BooleanInput} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; -import { - Directive, - ElementRef, - HostListener, - Inject, - NgZone, - Optional, - ViewChild -} from '@angular/core'; +import {Directive, ElementRef, HostListener, NgZone, ViewChild} from '@angular/core'; import { CanColor, CanColorCtor, @@ -29,7 +22,6 @@ import { mixinDisableRipple, RippleAnimationConfig } from '@angular/material/core'; -import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; import {numbers} from '@material/ripple'; /** Inputs common to all buttons. */ @@ -104,8 +96,7 @@ export class MatButtonBase extends _MatButtonBaseMixin implements CanDisable, Ca constructor( elementRef: ElementRef, public _platform: Platform, public _ngZone: NgZone, - // TODO(devversion): Injection can be removed if angular/angular#32981 is fixed. - @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) { + public _animationMode?: string) { super(elementRef); const classList = (elementRef.nativeElement as HTMLElement).classList; @@ -134,6 +125,9 @@ export class MatButtonBase extends _MatButtonBaseMixin implements CanDisable, Ca _isRippleDisabled() { return this.disableRipple || this.disabled; } + + static ngAcceptInputType_disabled: BooleanInput; + static ngAcceptInputType_disableRipple: BooleanInput; } /** Shared inputs by buttons using the `` tag */ @@ -163,8 +157,7 @@ export class MatAnchorBase extends MatButtonBase { tabIndex: number; constructor(elementRef: ElementRef, platform: Platform, ngZone: NgZone, - // TODO(devversion): Injection can be removed if angular/angular#32981 is fixed. - @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { + animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } diff --git a/src/material-experimental/mdc-button/button.scss b/src/material-experimental/mdc-button/button.scss index 84824b4b56f2..20021f4e60a3 100644 --- a/src/material-experimental/mdc-button/button.scss +++ b/src/material-experimental/mdc-button/button.scss @@ -1,6 +1,6 @@ -@import '@material/button/mixins'; -@import '@material/button/variables'; -@import '@material/ripple/mixins'; +@import '@material/button/mixins.import'; +@import '@material/button/variables.import'; +@import '@material/ripple/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @import '../../cdk/a11y/a11y'; @import '_button-base'; @@ -9,19 +9,24 @@ .mat-mdc-button, .mat-mdc-unelevated-button, .mat-mdc-raised-button, .mat-mdc-outlined-button { @include mdc-button-density(0, $mat-base-styles-query); - - // 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 { - &:not(.mdc-button--outlined) { - outline: solid 1px; - } - } - @include _mat-button-interactive(); @include _mat-button-disabled(); } +// 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. +.mat-mdc-button:not(.mdc-button--outlined), +.mat-mdc-unelevated-button:not(.mdc-button--outlined), +.mat-mdc-raised-button:not(.mdc-button--outlined), +.mat-mdc-outlined-button:not(.mdc-button--outlined), +.mat-mdc-fab, +.mat-mdc-mini-fab, +.mat-mdc-icon-button { + @include cdk-high-contrast(active, off) { + outline: solid 1px; + } +} + // Since the stroked button has has an actual border that reduces the available space for // child elements such as the ripple container or focus overlay, an inherited border radius // for the absolute-positioned child elements does not work properly. This is because the diff --git a/src/material-experimental/mdc-button/button.ts b/src/material-experimental/mdc-button/button.ts index 6dfabc6a3581..2758938f8ad8 100644 --- a/src/material-experimental/mdc-button/button.ts +++ b/src/material-experimental/mdc-button/button.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; import { ChangeDetectionStrategy, @@ -56,9 +55,6 @@ export class MatButton extends MatButtonBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } /** @@ -87,7 +83,4 @@ export class MatAnchor extends MatAnchorBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-button/fab.scss b/src/material-experimental/mdc-button/fab.scss index 924f66de7e68..a23a4d05aa04 100644 --- a/src/material-experimental/mdc-button/fab.scss +++ b/src/material-experimental/mdc-button/fab.scss @@ -1,6 +1,6 @@ -@import '@material/fab/mixins'; -@import '@material/button/variables'; -@import '@material/theme/variables'; +@import '@material/fab/mixins.import'; +@import '@material/button/variables.import'; +@import '@material/theme/variables.import'; @import '../mdc-helpers/mdc-helpers'; @import '_button-base'; diff --git a/src/material-experimental/mdc-button/fab.ts b/src/material-experimental/mdc-button/fab.ts index c4be202b301b..c8bcdfe5ad76 100644 --- a/src/material-experimental/mdc-button/fab.ts +++ b/src/material-experimental/mdc-button/fab.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; import { ChangeDetectionStrategy, @@ -55,9 +54,6 @@ export class MatFabButton extends MatButtonBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } @@ -87,7 +83,4 @@ export class MatFabAnchor extends MatAnchor { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-button/icon-button.scss b/src/material-experimental/mdc-button/icon-button.scss index c541891a41a9..ac18a6c91571 100644 --- a/src/material-experimental/mdc-button/icon-button.scss +++ b/src/material-experimental/mdc-button/icon-button.scss @@ -1,4 +1,4 @@ -@import '@material/icon-button/mixins'; +@import '@material/icon-button/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @import '_button-base'; diff --git a/src/material-experimental/mdc-button/icon-button.ts b/src/material-experimental/mdc-button/icon-button.ts index 07784d922825..963b74ecc4a4 100644 --- a/src/material-experimental/mdc-button/icon-button.ts +++ b/src/material-experimental/mdc-button/icon-button.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; import { ChangeDetectionStrategy, @@ -52,9 +51,6 @@ export class MatIconButton extends MatButtonBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } /** @@ -81,7 +77,4 @@ export class MatIconAnchor extends MatAnchorBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, platform, ngZone, animationMode); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-button/module.ts b/src/material-experimental/mdc-button/module.ts index e78a783ea55e..73fece9a031f 100644 --- a/src/material-experimental/mdc-button/module.ts +++ b/src/material-experimental/mdc-button/module.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatRippleModule} from '@angular/material/core'; import {MatAnchor, MatButton} from './button'; @@ -14,7 +13,7 @@ import {MatFabAnchor, MatFabButton} from './fab'; import {MatIconAnchor, MatIconButton} from './icon-button'; @NgModule({ - imports: [MatCommonModule, CommonModule, MatRippleModule], + imports: [MatCommonModule, MatRippleModule], exports: [ MatAnchor, MatButton, diff --git a/src/material-experimental/mdc-card/_mdc-card.scss b/src/material-experimental/mdc-card/_mdc-card.scss index 37c329db2945..42cbe9e8e804 100644 --- a/src/material-experimental/mdc-card/_mdc-card.scss +++ b/src/material-experimental/mdc-card/_mdc-card.scss @@ -1,5 +1,5 @@ -@import '@material/card/mixins'; -@import '@material/typography/mixins'; +@import '@material/card/mixins.import'; +@import '@material/typography/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-card-theme-mdc($theme) { diff --git a/src/material-experimental/mdc-card/card.scss b/src/material-experimental/mdc-card/card.scss index cf9922f1e0a9..2457dfad39f7 100644 --- a/src/material-experimental/mdc-card/card.scss +++ b/src/material-experimental/mdc-card/card.scss @@ -1,4 +1,4 @@ -@import '@material/card/mixins'; +@import '@material/card/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @import '../../cdk/a11y/a11y'; @@ -16,7 +16,7 @@ $mat-card-default-padding: 16px !default; // Add styles to the root card to have an outline in high-contrast mode. // TODO(jelbourn): file bug for MDC supporting high-contrast mode on `.mdc-card`. .mat-mdc-card { - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material-experimental/mdc-checkbox/_mdc-checkbox.scss b/src/material-experimental/mdc-checkbox/_mdc-checkbox.scss index 6eda50cd6c92..b2d314a24462 100644 --- a/src/material-experimental/mdc-checkbox/_mdc-checkbox.scss +++ b/src/material-experimental/mdc-checkbox/_mdc-checkbox.scss @@ -1,8 +1,8 @@ -@import '@material/checkbox/mixins'; -@import '@material/checkbox/variables'; -@import '@material/form-field/mixins'; -@import '@material/ripple/variables'; -@import '@material/theme/functions'; +@import '@material/checkbox/mixins.import'; +@import '@material/checkbox/variables.import'; +@import '@material/form-field/mixins.import'; +@import '@material/ripple/variables.import'; +@import '@material/theme/functions.import'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-checkbox-theme-mdc($theme) { diff --git a/src/material-experimental/mdc-checkbox/checkbox.scss b/src/material-experimental/mdc-checkbox/checkbox.scss index f685ac09984d..48747baaf0f3 100644 --- a/src/material-experimental/mdc-checkbox/checkbox.scss +++ b/src/material-experimental/mdc-checkbox/checkbox.scss @@ -1,7 +1,7 @@ -@import '@material/checkbox/functions'; -@import '@material/checkbox/mixins'; -@import '@material/form-field/mixins'; -@import '@material/ripple/variables'; +@import '@material/checkbox/functions.import'; +@import '@material/checkbox/mixins.import'; +@import '@material/form-field/mixins.import'; +@import '@material/ripple/variables.import'; @import '../mdc-helpers/mdc-helpers'; @include mdc-checkbox-without-ripple($query: $mat-base-styles-query); diff --git a/src/material-experimental/mdc-checkbox/checkbox.ts b/src/material-experimental/mdc-checkbox/checkbox.ts index 05543852b90d..05987cdffa81 100644 --- a/src/material-experimental/mdc-checkbox/checkbox.ts +++ b/src/material-experimental/mdc-checkbox/checkbox.ts @@ -363,7 +363,11 @@ export class MatCheckbox implements AfterViewInit, OnDestroy, ControlValueAccess /** Gets the value for the `aria-checked` attribute of the native input. */ _getAriaChecked(): 'true'|'false'|'mixed' { - return this.checked ? 'true' : (this.indeterminate ? 'mixed' : 'false'); + if (this.checked) { + return 'true'; + } + + return this.indeterminate ? 'mixed' : 'false'; } /** Sets whether the given CSS class should be applied to the native input. */ diff --git a/src/material-experimental/mdc-chips/BUILD.bazel b/src/material-experimental/mdc-chips/BUILD.bazel index 95fa96cb17a4..8c10db5096d5 100644 --- a/src/material-experimental/mdc-chips/BUILD.bazel +++ b/src/material-experimental/mdc-chips/BUILD.bazel @@ -73,6 +73,7 @@ ng_test_library( "@npm//@angular/common", "@npm//@angular/forms", "@npm//@angular/platform-browser", + "@npm//@material/chips", "@npm//material-components-web", "@npm//rxjs", ], diff --git a/src/material-experimental/mdc-chips/_mdc-chips.scss b/src/material-experimental/mdc-chips/_mdc-chips.scss index 795a46c337e7..bcaee6664d9e 100644 --- a/src/material-experimental/mdc-chips/_mdc-chips.scss +++ b/src/material-experimental/mdc-chips/_mdc-chips.scss @@ -1,6 +1,6 @@ -@import '@material/chips/mixins'; +@import '@material/chips/mixins.import'; @import '../mdc-helpers/mdc-helpers'; -@import '@material/theme/functions'; +@import '@material/theme/functions.import'; @mixin mat-chips-theme-mdc($theme) { @include mdc-chip-set-core-styles($query: $mat-theme-styles-query); diff --git a/src/material-experimental/mdc-chips/chip-grid.spec.ts b/src/material-experimental/mdc-chips/chip-grid.spec.ts index ec4b97289cea..e2ec4bbb2ab1 100644 --- a/src/material-experimental/mdc-chips/chip-grid.spec.ts +++ b/src/material-experimental/mdc-chips/chip-grid.spec.ts @@ -7,7 +7,9 @@ import { LEFT_ARROW, RIGHT_ARROW, SPACE, - TAB + TAB, + HOME, + END } from '@angular/cdk/keycodes'; import { createFakeEvent, @@ -103,15 +105,6 @@ describe('MDC-based MatChipGrid', () => { expect(chipGridNativeElement.hasAttribute('role')).toBe(false); }); - - it('should not set aria-required when it does not have a role', () => { - testComponent.chips = []; - fixture.detectChanges(); - - expect(chipGridNativeElement.hasAttribute('role')).toBe(false); - expect(chipGridNativeElement.hasAttribute('aria-required')).toBe(false); - }); - }); describe('focus behaviors', () => { @@ -452,6 +445,55 @@ describe('MDC-based MatChipGrid', () => { expect(manager.activeRowIndex).toBe(0); expect(manager.activeColumnIndex).toBe(0); }); + + it('should move focus to the first chip when pressing HOME', () => { + setupStandardGrid(); + manager = chipGridInstance._keyManager; + + const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); + const lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement; + + const HOME_EVENT: KeyboardEvent = + createKeyboardEvent('keydown', HOME, undefined, lastNativeChip); + const array = chips.toArray(); + const lastItem = array[array.length - 1]; + + lastItem.focus(); + expect(manager.activeRowIndex).toBe(4); + expect(manager.activeColumnIndex).toBe(0); + + chipGridInstance._keydown(HOME_EVENT); + fixture.detectChanges(); + + expect(HOME_EVENT.defaultPrevented).toBe(true); + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); + }); + + it('should move focus to the last chip when pressing END', () => { + setupStandardGrid(); + manager = chipGridInstance._keyManager; + + const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); + const firstNativeChip = nativeChips[0] as HTMLElement; + + const END_EVENT: KeyboardEvent = + createKeyboardEvent('keydown', END, undefined, firstNativeChip); + const array = chips.toArray(); + const firstItem = array[0]; + + firstItem.focus(); + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); + + chipGridInstance._keydown(END_EVENT); + fixture.detectChanges(); + + expect(END_EVENT.defaultPrevented).toBe(true); + expect(manager.activeRowIndex).toBe(4); + expect(manager.activeColumnIndex).toBe(0); + }); + }); }); diff --git a/src/material-experimental/mdc-chips/chip-grid.ts b/src/material-experimental/mdc-chips/chip-grid.ts index d7e90efa33d8..436dae1bafbb 100644 --- a/src/material-experimental/mdc-chips/chip-grid.ts +++ b/src/material-experimental/mdc-chips/chip-grid.ts @@ -8,7 +8,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; -import {BACKSPACE, TAB} from '@angular/cdk/keycodes'; +import {BACKSPACE, TAB, HOME, END} from '@angular/cdk/keycodes'; import { AfterContentInit, AfterViewInit, @@ -87,7 +87,6 @@ const _MatChipGridMixinBase: CanUpdateErrorStateCtor & typeof MatChipGridBase = '[tabIndex]': '_chips && _chips.length === 0 ? -1 : tabIndex', // TODO: replace this binding with use of AriaDescriber '[attr.aria-describedby]': '_ariaDescribedby || null', - '[attr.aria-required]': 'role ? required : null', '[attr.aria-disabled]': 'disabled.toString()', '[attr.aria-invalid]': 'errorState', '[class.mat-mdc-chip-list-disabled]': 'disabled', @@ -401,17 +400,27 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn /** Handles custom keyboard events. */ _keydown(event: KeyboardEvent) { const target = event.target as HTMLElement; + const keyCode = event.keyCode; + const manager = this._keyManager; // If they are on an empty input and hit backspace, focus the last chip - if (event.keyCode === BACKSPACE && this._isEmptyInput(target)) { + if (keyCode === BACKSPACE && this._isEmptyInput(target)) { if (this._chips.length) { - this._keyManager.setLastCellActive(); + manager.setLastCellActive(); } event.preventDefault(); - } else if (event.keyCode === TAB && target.id !== this._chipInput!.id ) { + } else if (keyCode === TAB && target.id !== this._chipInput!.id ) { this._allowFocusEscape(); } else if (this._originatesFromChip(event)) { - this._keyManager.onKeydown(event); + if (keyCode === HOME) { + manager.setFirstCellActive(); + event.preventDefault(); + } else if (keyCode === END) { + manager.setLastCellActive(); + event.preventDefault(); + } else { + manager.onKeydown(event); + } } this.stateChanges.next(); } diff --git a/src/material-experimental/mdc-chips/chip-icons.ts b/src/material-experimental/mdc-chips/chip-icons.ts index 01c2b3bb20ab..10522c78aa23 100644 --- a/src/material-experimental/mdc-chips/chip-icons.ts +++ b/src/material-experimental/mdc-chips/chip-icons.ts @@ -84,7 +84,7 @@ const _MatChipRemoveMixinBase: CanDisableCtor & HasTabIndexCtor & typeof MatChipRemoveBase = - mixinTabIndex(mixinDisabled(MatChipRemoveBase)); + mixinTabIndex(mixinDisabled(MatChipRemoveBase), 0); /** * Directive to remove the parent chip when the trailing icon is clicked or @@ -95,9 +95,11 @@ const _MatChipRemoveMixinBase: * * Example: * - * ` - * cancel - * ` + * ``` + * + * cancel + * + * ``` */ @Directive({ selector: '[matChipRemove]', @@ -109,6 +111,12 @@ const _MatChipRemoveMixinBase: 'role': 'button', '(click)': 'interaction.next($event)', '(keydown)': 'interaction.next($event)', + + // Prevent accidental form submissions. + 'type': 'button', + + // We need to remove this explicitly, because it gets inherited from MatChipTrailingIcon. + '[attr.aria-hidden]': 'null', } }) export class MatChipRemove extends _MatChipRemoveMixinBase implements CanDisable, HasTabIndex { diff --git a/src/material-experimental/mdc-chips/chip-input.spec.ts b/src/material-experimental/mdc-chips/chip-input.spec.ts index c0ec4a2f5d01..2dea7ca586de 100644 --- a/src/material-experimental/mdc-chips/chip-input.spec.ts +++ b/src/material-experimental/mdc-chips/chip-input.spec.ts @@ -95,7 +95,7 @@ describe('MDC-based MatChipInput', () => { expect(label.textContent).toContain('or don\'t'); }); - it('should become disabled if the chip list is disabled', () => { + it('should become disabled if the chip grid is disabled', () => { expect(inputNativeElement.hasAttribute('disabled')).toBe(false); expect(chipInputDirective.disabled).toBe(false); @@ -106,6 +106,15 @@ describe('MDC-based MatChipInput', () => { expect(chipInputDirective.disabled).toBe(true); }); + it('should be aria-required if the chip grid is required', () => { + expect(inputNativeElement.hasAttribute('aria-required')).toBe(false); + + fixture.componentInstance.required = true; + fixture.detectChanges(); + + expect(inputNativeElement.getAttribute('aria-required')).toBe('true'); + }); + it('should allow focus to escape when tabbing forwards', fakeAsync(() => { const gridElement: HTMLElement = fixture.nativeElement.querySelector('mat-chip-grid'); @@ -249,7 +258,7 @@ describe('MDC-based MatChipInput', () => { @Component({ template: ` - + Hello { expect(chipNativeElement.getAttribute('aria-disabled')).toBe('true'); }); }); + + it('should hide the leading icon when initialized as selected', () => { + // We need to recreate the fixture before change detection has + // run so we can capture the behavior we're testing for. + fixture.destroy(); + fixture = TestBed.createComponent(SingleChip); + testComponent = fixture.debugElement.componentInstance; + testComponent.selected = true; + fixture.detectChanges(); + chipDebugElement = fixture.debugElement.query(By.directive(MatChipOption))!; + chipNativeElement = chipDebugElement.nativeElement; + chipInstance = chipDebugElement.injector.get(MatChipOption); + + const avatar = fixture.nativeElement.querySelector('.avatar'); + expect(avatar.classList).toContain(chipCssClasses.HIDDEN_LEADING_ICON); + }); }); }); @@ -280,6 +297,7 @@ describe('MDC-based Option Chips', () => { [color]="color" [selected]="selected" [disabled]="disabled" (focus)="chipFocus($event)" (destroyed)="chipDestroy($event)" (selectionChange)="chipSelectionChange($event)"> + {{name}}
    diff --git a/src/material-experimental/mdc-chips/chip-option.ts b/src/material-experimental/mdc-chips/chip-option.ts index 8dc011468e8d..6f99dc129795 100644 --- a/src/material-experimental/mdc-chips/chip-option.ts +++ b/src/material-experimental/mdc-chips/chip-option.ts @@ -14,8 +14,10 @@ import { EventEmitter, Input, Output, - ViewEncapsulation + ViewEncapsulation, + AfterContentInit } from '@angular/core'; +import {chipCssClasses} from '@material/chips'; import {take} from 'rxjs/operators'; import {MatChip} from './chip'; @@ -61,7 +63,7 @@ export class MatChipSelectionChange { encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MatChipOption extends MatChip { +export class MatChipOption extends MatChip implements AfterContentInit { /** Whether the chip list is selectable. */ chipListSelectable: boolean = true; @@ -116,6 +118,14 @@ export class MatChipOption extends MatChip { @Output() readonly selectionChange: EventEmitter = new EventEmitter(); + ngAfterContentInit() { + super.ngAfterContentInit(); + + if (this.selected && this.leadingIcon) { + this.leadingIcon.setClass(chipCssClasses.HIDDEN_LEADING_ICON, true); + } + } + /** Selects the chip. */ select(): void { if (!this.selectable) { @@ -227,8 +237,4 @@ export class MatChipOption extends MatChip { static ngAcceptInputType_selectable: BooleanInput; static ngAcceptInputType_selected: BooleanInput; - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_removable: BooleanInput; - static ngAcceptInputType_highlighted: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-chips/chip-remove.spec.ts b/src/material-experimental/mdc-chips/chip-remove.spec.ts index a09db2aac7df..ab85e2160fb2 100644 --- a/src/material-experimental/mdc-chips/chip-remove.spec.ts +++ b/src/material-experimental/mdc-chips/chip-remove.spec.ts @@ -1,7 +1,13 @@ -import {createFakeEvent} from '@angular/cdk/testing/private'; +import { + createFakeEvent, + dispatchKeyboardEvent, + createKeyboardEvent, + dispatchEvent, +} from '@angular/cdk/testing/private'; import {Component, DebugElement} from '@angular/core'; import {By} from '@angular/platform-browser'; import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {SPACE, ENTER} from '@angular/cdk/keycodes'; import {MatChip, MatChipsModule} from './index'; describe('MDC-based Chip Remove', () => { @@ -37,6 +43,12 @@ describe('MDC-based Chip Remove', () => { expect(buttonElement.classList).toContain('mat-mdc-chip-remove'); }); + it('should ensure that the button cannot submit its parent form', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + expect(buttonElement.getAttribute('type')).toBe('button'); + }); + it('should start MDC exit animation on click', () => { let buttonElement = chipNativeElement.querySelector('button')!; @@ -78,6 +90,66 @@ describe('MDC-based Chip Remove', () => { expect(chipNativeElement.classList.contains('mdc-chip--exit')).toBe(false); }); + + it('should not make the element aria-hidden when it is focusable', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + expect(buttonElement.getAttribute('tabindex')).toBe('0'); + expect(buttonElement.hasAttribute('aria-hidden')).toBe(false); + }); + + it('should prevent the default SPACE action', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + testChip.removable = true; + fixture.detectChanges(); + + const event = dispatchKeyboardEvent(buttonElement, 'keydown', SPACE); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(true); + }); + + it('should not prevent the default SPACE action when a modifier key is pressed', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + testChip.removable = true; + fixture.detectChanges(); + + const event = createKeyboardEvent('keydown', SPACE); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + dispatchEvent(buttonElement, event); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(false); + }); + + it('should prevent the default ENTER action', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + testChip.removable = true; + fixture.detectChanges(); + + const event = dispatchKeyboardEvent(buttonElement, 'keydown', ENTER); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(true); + }); + + it('should not prevent the default ENTER action when a modifier key is pressed', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + testChip.removable = true; + fixture.detectChanges(); + + const event = createKeyboardEvent('keydown', ENTER); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + dispatchEvent(buttonElement, event); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(false); + }); + }); }); diff --git a/src/material-experimental/mdc-chips/chip-row.ts b/src/material-experimental/mdc-chips/chip-row.ts index 5ae2818247a6..60ac9a0a49d1 100644 --- a/src/material-experimental/mdc-chips/chip-row.ts +++ b/src/material-experimental/mdc-chips/chip-row.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {BACKSPACE, DELETE} from '@angular/cdk/keycodes'; import { AfterContentInit, @@ -149,9 +148,4 @@ export class MatChipRow extends MatChip implements AfterContentInit, AfterViewIn this._handleInteraction(event); } } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_removable: BooleanInput; - static ngAcceptInputType_highlighted: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-chips/chip-set.ts b/src/material-experimental/mdc-chips/chip-set.ts index c25152c27d9d..c78f40aa1e13 100644 --- a/src/material-experimental/mdc-chips/chip-set.ts +++ b/src/material-experimental/mdc-chips/chip-set.ts @@ -294,7 +294,8 @@ export class MatChipSet extends _MatChipSetMixinBase implements AfterContentInit let currentElement = event.target as HTMLElement | null; while (currentElement && currentElement !== this._elementRef.nativeElement) { - if (currentElement.classList.contains('mdc-chip')) { + // Null check the classList, because IE and Edge don't support it on all elements. + if (currentElement.classList && currentElement.classList.contains('mdc-chip')) { return true; } diff --git a/src/material-experimental/mdc-chips/chip.spec.ts b/src/material-experimental/mdc-chips/chip.spec.ts index 6858ccde061f..f5ed6a56612a 100644 --- a/src/material-experimental/mdc-chips/chip.spec.ts +++ b/src/material-experimental/mdc-chips/chip.spec.ts @@ -21,7 +21,12 @@ describe('MDC-based MatChip', () => { globalRippleOptions = {}; TestBed.configureTestingModule({ imports: [MatChipsModule], - declarations: [BasicChip, SingleChip], + declarations: [ + BasicChip, + SingleChip, + BasicChipWithStaticTabindex, + BasicChipWithBoundTabindex, + ], providers: [ {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions}, {provide: Directionality, useFactory: () => ({ @@ -35,18 +40,41 @@ describe('MDC-based MatChip', () => { })); describe('MatBasicChip', () => { - - beforeEach(() => { + it('adds the `mat-mdc-basic-chip` class', () => { fixture = TestBed.createComponent(BasicChip); fixture.detectChanges(); - chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!; - chipNativeElement = chipDebugElement.nativeElement; - chipInstance = chipDebugElement.injector.get(MatChip); + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.classList).toContain('mat-mdc-basic-chip'); }); - it('adds the `mat-mdc-basic-chip` class', () => { - expect(chipNativeElement.classList).toContain('mat-mdc-basic-chip'); + it('should be able to set a static tabindex', () => { + fixture = TestBed.createComponent(BasicChipWithStaticTabindex); + fixture.detectChanges(); + + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.getAttribute('tabindex')).toBe('3'); + }); + + it('should be able to set a static tabindex', () => { + fixture = TestBed.createComponent(BasicChipWithStaticTabindex); + fixture.detectChanges(); + + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.getAttribute('tabindex')).toBe('3'); + }); + + it('should be able to set a dynamic tabindex', () => { + fixture = TestBed.createComponent(BasicChipWithBoundTabindex); + fixture.detectChanges(); + + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.getAttribute('tabindex')).toBe('12'); + + fixture.componentInstance.tabindex = 15; + fixture.detectChanges(); + + expect(chip.getAttribute('tabindex')).toBe('15'); }); }); @@ -104,6 +132,17 @@ describe('MDC-based MatChip', () => { expect(testComponent.chipRemove).toHaveBeenCalledWith({chip: chipInstance}); }); + it('should make the chip non-focusable when it is removed', () => { + chipInstance.remove(); + fixture.detectChanges(); + + const fakeEvent = createFakeEvent('transitionend'); + (fakeEvent as any).propertyName = 'width'; + chipNativeElement.dispatchEvent(fakeEvent); + + expect(chipNativeElement.style.display).toBe('none'); + }); + it('should be able to disable ripples through ripple global options at runtime', () => { expect(chipInstance.rippleDisabled).toBe(false, 'Expected chip ripples to be enabled.'); @@ -173,7 +212,20 @@ class SingleChip { } @Component({ - template: `{{name}}` + template: `Hello` }) class BasicChip { } + +@Component({ + template: `Hello` +}) +class BasicChipWithStaticTabindex { +} + +@Component({ + template: `Hello` +}) +class BasicChipWithBoundTabindex { + tabindex = 12; +} diff --git a/src/material-experimental/mdc-chips/chip.ts b/src/material-experimental/mdc-chips/chip.ts index 07fb72b6db80..7983d5c9c5f2 100644 --- a/src/material-experimental/mdc-chips/chip.ts +++ b/src/material-experimental/mdc-chips/chip.ts @@ -46,6 +46,7 @@ import { } from '@angular/material/core'; import {MDCChipAdapter, MDCChipFoundation} from '@material/chips'; import {numbers} from '@material/ripple'; +import {SPACE, ENTER, hasModifierKey} from '@angular/cdk/keycodes'; import {Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; import {MatChipAvatar, MatChipTrailingIcon, MatChipRemove} from './chip-icons'; @@ -250,7 +251,10 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte addClassToLeadingIcon: (className) => this.leadingIcon.setClass(className, true), removeClassFromLeadingIcon: (className) => this.leadingIcon.setClass(className, false), eventTargetHasClass: (target: EventTarget | null, className: string) => { - return target ? (target as Element).classList.contains(className) : false; + // We need to null check the `classList`, because IE and Edge don't support it on SVG elements + // and Edge seems to throw for ripple elements, because they're outside the DOM. + return (target && (target as Element).classList) ? + (target as Element).classList.contains(className) : false; }, notifyInteraction: () => this.interaction.emit(this.id), notifySelection: () => { @@ -262,9 +266,22 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte // future. }, notifyTrailingIconInteraction: () => this.removeIconInteraction.emit(this.id), - notifyRemoval: () => this.removed.emit({chip: this}), - getComputedStyleValue: propertyName => - window.getComputedStyle(this._elementRef.nativeElement).getPropertyValue(propertyName), + notifyRemoval: () => { + this.removed.emit({ chip: this }); + + // When MDC removes a chip it just transitions it to `width: 0px` which means that it's still + // in the DOM and it's still focusable. Make it `display: none` so users can't tab into it. + this._elementRef.nativeElement.style.display = 'none'; + }, + getComputedStyleValue: propertyName => { + // This function is run when a chip is removed so it might be + // invoked during server-side rendering. Add some extra checks just in case. + if (typeof window !== 'undefined' && window) { + const getComputedStyle = window.getComputedStyle(this._elementRef.nativeElement); + return getComputedStyle.getPropertyValue(propertyName); + } + return ''; + }, setStyleProperty: (propertyName: string, value: string) => { this._elementRef.nativeElement.style.setProperty(propertyName, value); }, @@ -339,15 +356,28 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte _listenToRemoveIconInteraction() { this.removeIcon.interaction .pipe(takeUntil(this._destroyed)) - .subscribe((event) => { + .subscribe(event => { // The MDC chip foundation calls stopPropagation() for any trailing icon interaction // event, even ones it doesn't handle, so we want to avoid passing it keyboard events - // for which we have a custom handler. - if (this.disabled || (event instanceof KeyboardEvent && - this.HANDLED_KEYS.indexOf(event.keyCode) !== -1)) { + // for which we have a custom handler. Note that we assert the type of the event using + // the `type`, because `instanceof KeyboardEvent` can throw during server-side rendering. + const isKeyboardEvent = event.type.startsWith('key'); + + if (this.disabled || (isKeyboardEvent && + this.HANDLED_KEYS.indexOf((event as KeyboardEvent).keyCode) !== -1)) { return; } + this._chipFoundation.handleTrailingIconInteraction(event); + + if (isKeyboardEvent && !hasModifierKey(event as KeyboardEvent)) { + const keyCode = (event as KeyboardEvent).keyCode; + + // Prevent default space and enter presses so we don't scroll the page or submit forms. + if (keyCode === SPACE || keyCode === ENTER) { + event.preventDefault(); + } + } }); } diff --git a/src/material-experimental/mdc-chips/chips.scss b/src/material-experimental/mdc-chips/chips.scss index 352ef72d0e92..5c839470380e 100644 --- a/src/material-experimental/mdc-chips/chips.scss +++ b/src/material-experimental/mdc-chips/chips.scss @@ -1,12 +1,10 @@ -@import '@material/chips/mixins'; +@import '@material/chips/mixins.import'; @import '../../material/core/style/layout-common'; -@import '../../material/core/style/noop-animation'; @import '../../cdk/a11y/a11y'; @import '../mdc-helpers/mdc-helpers'; @include mdc-chip-without-ripple($query: $mat-base-styles-query); @include mdc-chip-set-core-styles($query: $mat-base-styles-query); -@include _noop-animation; .mat-mdc-chip { // MDC uses a pointer cursor @@ -16,7 +14,15 @@ // Required for the ripple to clip properly in Safari. transform: translateZ(0); - @include cdk-high-contrast { + &._mat-animation-noopable { + // MDC's chip removal works by toggling a class on the chip, waiting for its transitions + // to finish and emitting the remove event at the end. The problem is that if our animations + // were disabled via the `NoopAnimationsModule`, the element won't have a transition and + // `transitionend` won't fire. We work around the issue by assigning a very short transition. + transition-duration: 1ms; + } + + @include cdk-high-contrast(active, off) { outline: solid 1px; &:focus { @@ -71,3 +77,12 @@ $mat-chip-input-width: 150px; input.mat-mdc-chip-input { flex: 1 0 $mat-chip-input-width; } + +.mdc-chip__checkmark-path { + @include cdk-high-contrast(black-on-white, off) { + // 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 color + // to ensure that it's visible. We need !important, because the theme styles are very specific. + stroke: #000 !important; + } +} diff --git a/src/material-experimental/mdc-form-field/BUILD.bazel b/src/material-experimental/mdc-form-field/BUILD.bazel new file mode 100644 index 000000000000..2d99d1978734 --- /dev/null +++ b/src/material-experimental/mdc-form-field/BUILD.bazel @@ -0,0 +1,112 @@ +package(default_visibility = ["//visibility:public"]) + +load("//src/e2e-app:test_suite.bzl", "e2e_test_suite") +load( + "//tools:defaults.bzl", + "ng_e2e_test_library", + "ng_module", + "sass_binary", + "sass_library", +) + +ng_module( + name = "mdc-form-field", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + assets = [":form_field_scss"] + glob(["**/*.html"]), + module_name = "@angular/material-experimental/mdc-form-field", + deps = [ + "//src/cdk/observers", + "//src/cdk/platform", + "//src/material/core", + "//src/material/form-field", + "@npm//@angular/forms", + "@npm//@material/floating-label", + "@npm//@material/line-ripple", + "@npm//@material/textfield", + "@npm//rxjs", + ], +) + +sass_library( + name = "mdc_form_field_scss_lib", + srcs = [ + "_mdc-form-field.scss", + "_mdc-text-field-theme-variable-refresh.scss", + ], + deps = [ + ":form_field_partials", + "//src/cdk/a11y:a11y_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + ], +) + +sass_binary( + name = "form_field_scss", + src = "form-field.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + ":form_field_partials", + ":mdc_form_field_scss", + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], +) + +# TODO(devversion): Import all of the individual Sass mixins once feature targeting is available +# for MDC text-field, notched-outline, floating-label and line-ripple. +sass_library( + name = "mdc_form_field_scss", + srcs = [ + "@npm//:node_modules/@material/floating-label/mdc-floating-label.scss", + "@npm//:node_modules/@material/line-ripple/mdc-line-ripple.scss", + "@npm//:node_modules/@material/notched-outline/mdc-notched-outline.scss", + "@npm//:node_modules/@material/ripple/common.scss", + "@npm//:node_modules/@material/textfield/_functions.scss", + "@npm//:node_modules/@material/textfield/character-counter/mdc-text-field-character-counter.scss", + "@npm//:node_modules/@material/textfield/helper-text/mdc-text-field-helper-text.scss", + "@npm//:node_modules/@material/textfield/icon/mdc-text-field-icon.scss", + "@npm//:node_modules/@material/textfield/mdc-text-field.scss", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], +) + +sass_library( + name = "form_field_partials", + srcs = [ + "_form-field-bottom-line.scss", + "_form-field-sizing.scss", + "_form-field-subscript.scss", + "_mdc-text-field-structure-overrides.scss", + "_mdc-text-field-textarea-overrides.scss", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], +) + +########### +# Testing +########### + +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-form-field/README.md b/src/material-experimental/mdc-form-field/README.md new file mode 100644 index 000000000000..92c84ec0b6f6 --- /dev/null +++ b/src/material-experimental/mdc-form-field/README.md @@ -0,0 +1 @@ +This is a placeholder for the MDC-based implementation of form-field. diff --git a/src/material-experimental/mdc-form-field/_form-field-bottom-line.scss b/src/material-experimental/mdc-form-field/_form-field-bottom-line.scss new file mode 100644 index 000000000000..765905659cff --- /dev/null +++ b/src/material-experimental/mdc-form-field/_form-field-bottom-line.scss @@ -0,0 +1,36 @@ +@import 'form-field-sizing'; +@import '../mdc-helpers/mdc-helpers'; + +@mixin _mat-form-field-bottom-line() { + // Bottom line for form-fields. MDC by default only has a bottom-line for inputs + // and textareas. This does not work for us because we support abstract form-field + // controls which might not render an input or textarea. Additionally, the default MDC + // bottom-line does only cover the width of the input, while we also want it to cover + // prefixes and suffixes. + .mat-mdc-form-field-bottom-line { + position: absolute; + bottom: 0; + width: 100%; + height: 1px; + border-bottom-width: 1px; + border-bottom-style: solid; + } +} + +@mixin _mat-form-field-bottom-line-theme() { + // Sets the color of the bottom-line. Our custom bottom-line is based on the default + // MDC bottom-line that only works for inputs and textareas (hence we need a custom + // bottom-line that works for all type of form-field controls). To replicate the + // appearance of the default MDC bottom-line, we use the same theming variables. + .mat-mdc-form-field-bottom-line { + @include mdc-theme-prop(border-bottom-color, $mdc-text-field-bottom-line-idle); + + .mdc-text-field--disabled & { + @include mdc-theme-prop(border-bottom-color, $mdc-text-field-disabled-border); + } + + .mdc-text-field--invalid & { + @include mdc-theme-prop(border-bottom-color, $mdc-text-field-error); + } + } +} diff --git a/src/material-experimental/mdc-form-field/_form-field-sizing.scss b/src/material-experimental/mdc-form-field/_form-field-sizing.scss new file mode 100644 index 000000000000..0053ad4f81f4 --- /dev/null +++ b/src/material-experimental/mdc-form-field/_form-field-sizing.scss @@ -0,0 +1,37 @@ +@import '@material/textfield/variables.import'; + +// Top spacing of the form-field outline. MDC does not have a variable for this +// and just hard-codes it into their styles. +$mat-form-field-outline-top-spacing: 12px; + +// Baseline based on the default height of the MDC text-field. +$mat-form-field-baseline: $mdc-text-field-height / 2; + +// Infix stretches to fit the container, but naturally wants to be this wide. We set +// this in order to have a a consistent natural size for the various types of controls +// that can go in a form field. +$mat-form-field-default-infix-width: 180px !default; + +// Minimum amount of space between start and end hints in the subscript. MDC does not +// have built-in support for hints. +$mat-form-field-hint-min-space: 1em !default; + +// Vertical spacing of the text-field if there is no label. MDC hard-codes the spacing +// into their styles, but their spacing variables would not work for our form-field +// structure anyway. This is because MDC's input elements are larger than the text, and +// their padding variables are calculated with respect to the vertical empty space of the +// inputs. We take the explicit numbers provided by the Material Design specification. +// https://material.io/components/text-fields/#specs +$mat-form-field-no-label-padding-bottom: 16px; +$mat-form-field-no-label-padding-top: 20px; + +// Vertical spacing of the text-field if there is a label. MDC hard-codes the spacing +// into their styles, but their spacing variables would not work for our form-field +// structure anyway. This is because MDC's input elements are larger than the text, and +// their padding variables are calculated with respect to the vertical empty space of the +// inputs. We take the numbers provided by the Material Design specification. **Note** that +// the drawn values in the spec are slightly shifted because the spec assumes that typed input +// text exceeds the input element boundaries. We account for this since typed input text does +// not overflow in browsers by default. +$mat-form-field-with-label-input-padding-top: 24px; +$mat-form-field-with-label-input-padding-bottom: 12px; diff --git a/src/material-experimental/mdc-form-field/_form-field-subscript.scss b/src/material-experimental/mdc-form-field/_form-field-subscript.scss new file mode 100644 index 000000000000..2e8af414919a --- /dev/null +++ b/src/material-experimental/mdc-form-field/_form-field-subscript.scss @@ -0,0 +1,67 @@ +@import 'form-field-sizing'; +@import '../mdc-helpers/mdc-helpers'; + +@mixin _mat-form-field-subscript() { + // Wrapper for the hints and error messages. + .mat-mdc-form-field-subscript-wrapper { + box-sizing: border-box; + width: 100%; + // prevents multi-line errors from overlapping the control. + overflow: hidden; + } + + // Scale down icons in the subscript to be the same size as the text. + .mat-mdc-form-field-subscript-wrapper .mat-icon { + width: 1em; + height: 1em; + font-size: inherit; + vertical-align: baseline; + } + + // Clears the floats on the hints. This is necessary for the hint animation to work. + .mat-mdc-form-field-hint-wrapper { + display: flex; + } + + // Spacer used to make sure start and end hints have enough space between them. + .mat-mdc-form-field-hint-spacer { + flex: 1 0 $mat-form-field-hint-min-space; + } + + // Single error message displayed beneath the form field underline. + .mat-mdc-form-field-error { + display: block; + } +} + +@mixin _mat-form-field-subscript-theme() { + // MDC does not have built-in error treatment. + .mat-mdc-form-field-error { + @include mdc-theme-prop(color, $mdc-text-field-error); + } +} + +@mixin _mat-form-field-subscript-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the subscript. + $subscript-font-scale: 0.75; + // Font size to use for the subscript text. + $subscript-font-size: $subscript-font-scale * 100%; + // The space between the bottom of the text-field area and the subscript. Mocks in the spec show + // half of the text size, but this margin is applied to an element with the subscript text font + // size, so we need to divide by the scale factor to make it half of the original text size. + $subscript-margin-top: 0.5em / $subscript-font-scale; + // The minimum height applied to the subscript to reserve space for subscript text. This is a + // combination of the subscript's margin and line-height, but we need to multiply by the + // subscript font scale factor since the subscript has a reduced font size. + $subscript-min-height: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + // The subscript wrapper has a minimum height to avoid that the form-field + // jumps when hints or errors are displayed. + .mat-mdc-form-field-subscript-wrapper { + min-height: $subscript-min-height; + font-size: $subscript-font-size; + margin-top: $subscript-margin-top; + } +} diff --git a/src/material-experimental/mdc-form-field/_mdc-form-field.scss b/src/material-experimental/mdc-form-field/_mdc-form-field.scss new file mode 100644 index 000000000000..289e76ee1523 --- /dev/null +++ b/src/material-experimental/mdc-form-field/_mdc-form-field.scss @@ -0,0 +1,40 @@ +@use '@material/ripple/mixins' as mdc-ripple; + +@import '@material/textfield/mixins.import'; +@import '../mdc-helpers/mdc-helpers'; +@import 'form-field-subscript'; +@import 'form-field-bottom-line'; +@import 'mdc-text-field-theme-variable-refresh'; + +@mixin mat-form-field-theme-mdc($theme) { + @include mat-using-mdc-theme($theme) { + @include _mdc-text-field-refresh-theme-variables() { + @include mdc-text-field-core-styles($query: $mat-theme-styles-query); + @include mdc-floating-label-core-styles($query: $mat-theme-styles-query); + @include mdc-text-field-core-styles($query: $mat-theme-styles-query); + @include _mat-form-field-subscript-theme(); + @include _mat-form-field-bottom-line-theme(); + + // MDC text-field intends to hide the ripples in the outline appearance. The styles for + // this collide with other styles from the structure styles. This is because the ripples + // are made invisible by using the `mdc-ripple.states-base-color` mixin. The mixin makes the + // ripples `transparent` by generating `content: none` instead. This means that the style + // will collide with the default `content` for ripple pseudo elements. Depending on how + // themes and component styles are inserted, the ripples will not hide properly. To ensure + // that the ripples are not rendered in the outline appearance, we copy the mixin call but + // increase the specificity. + .mat-mdc-text-field-wrapper.mdc-text-field--outlined { + @include mdc-ripple.states-base-color(transparent); + } + } + } +} + +@mixin mat-form-field-typography-mdc($config) { + @include mat-using-mdc-typography($config) { + @include mdc-text-field-core-styles($query: $mat-typography-styles-query); + @include mdc-floating-label-core-styles($query: $mat-typography-styles-query); + @include mdc-text-field-core-styles($query: $mat-typography-styles-query); + @include _mat-form-field-subscript-typography($config); + } +} diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss new file mode 100644 index 000000000000..e3e0a77e27f0 --- /dev/null +++ b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss @@ -0,0 +1,106 @@ +@import 'form-field-sizing'; + +// Mixin that can be included to override the default MDC text-field +// styles to fit our needs. See individual comments for context on why +// certain MDC styles need to be modified. +@mixin _mat-mdc-text-field-structure-overrides() { + // Always hide the asterisk displayed by MDC. This is necessary because MDC can only display + // the asterisk if the label is directly preceded by the input. MDC does this because it + // relies on CSS to figure out if the input/textarea is required. This does not apply for us + // because it's not guaranteed that the form control is an input/textarea. The required state + // is handled as part of the registered form-field control instance. The asterisk will be + // rendered conditionally through the floating label. + .mat-mdc-form-field .mdc-floating-label::after { + display: none; + } + + // Unset the border set by MDC. We move the border (which serves as the Material Design + // text-field bottom line) into its own element. This is necessary because we want the + // bottom-line to span across the whole form-field (including prefixes and suffixes). + // Also we want to ensure that font styles are inherited for input elements. We want input + // text to align with surrounding text. Also font inheritance has been enabled in the non + // MDC-based implementation of the form-field too, so we need it for backwards compatibility. + .mat-mdc-input-element { + font: inherit; + border: none; + } + + // Reset the padding that MDC sets on native input elements. We cannot rely on this + // spacing as we support arbitrary form-field controls which aren't necessarily matching + // the "mdc-text-field__input" class. Note: We need the first selector to overwrite the + // default no-label MDC padding styles which are set with a very high specificity. + .mdc-text-field--no-label:not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) + .mat-mdc-input-element.mdc-text-field__input, + .mat-mdc-input-element { + padding: 0; + } + + // MDC changes the vertical spacing of the input if there is no label. Since we moved + // the spacing of the input to the parent infix container (to support custom controls), + // we need to replicate these styles for the infix container. The goal is that the input + // placeholder vertically aligns with floating labels of other filled inputs. Note that + // outline appearance currently still relies on the input spacing due to a notched-outline + // limitation. TODO: https://github.com/material-components/material-components-web/issues/5326 + .mdc-text-field--no-label:not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) + .mat-mdc-form-field-infix { + padding-top: $mat-form-field-no-label-padding-top; + padding-bottom: $mat-form-field-no-label-padding-bottom; + } + + // MDC adds vertical spacing to inputs. We removed this spacing and intend to add it + // to the infix container. This is necessary to ensure that custom form-field controls + // also have the proper Material Design spacing to the label and bottom-line. Note that + // outline appearance currently still relies on the input spacing due to a notched-outline + // limitation. TODO: https://github.com/material-components/material-components-web/issues/5326 + .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) .mat-mdc-form-field-infix { + // Apply the default text-field input padding to the infix container. We removed the + // padding from the input elements in order to support arbitrary form-field controls. + padding-top: $mat-form-field-with-label-input-padding-top; + padding-bottom: $mat-form-field-with-label-input-padding-bottom; + } + + // Root element of the mdc-text-field. As explained in the height overwrites above, MDC + // sets a default height on the text-field root element. This is not desired since we + // want the element to be able to expand as needed. + .mat-mdc-text-field-wrapper { + height: auto; + flex: auto; + } + + // The default MDC text-field implementation does not support labels which always float. + // MDC only renders the placeholder if the input is focused. We extend this to show the + // placeholder if the form-field label is set to always float. + // TODO(devversion): consider getting a mixin or variables for this (currently not available). + // Stylelint no-prefixes rule disabled because MDC text-field only uses "::placeholder" too. + /* stylelint-disable-next-line material/no-prefixes */ + .mat-mdc-form-field-label-always-float .mdc-text-field__input::placeholder { + transition-delay: 40ms; + transition-duration: 110ms; + opacity: 1; + } + + // The additional nesting is a temporary until the notched-outline is decoupled from the + // floating label. See https://github.com/material-components/material-components-web/issues/5326 + // TODO(devversion): Remove this workaround/nesting once the feature is available. + .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) { + // We removed the horizontal inset on input elements, but need to re-add the spacing to + // the actual form-field flex container that contains the prefixes, suffixes and infix. + .mat-mdc-form-field-flex { + padding: 0 $mdc-text-field-input-padding; + } + + // Since we moved the horizontal spacing from the input to the form-field flex container + // and the MDC floating label tries to account for the horizontal spacing, we need to reset + // the shifting since there is no padding the label needs to account for. + .mdc-floating-label { + left: 0; + } + } + + // MDC sets the input elements in outline appearance to "display: flex". There seems to + // be no particular reason why this is needed. We set it to "inline-block", as it otherwise + // could shift the baseline. + .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mdc-text-field__input { + display: inline-block; + } +} diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss b/src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss new file mode 100644 index 000000000000..5714cbde6a11 --- /dev/null +++ b/src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss @@ -0,0 +1,51 @@ +@import 'form-field-sizing'; + +// MDCs default textarea styles cannot be used because they only apply if a special +// class is applied to the "mdc-text-field" wrapper. Since we cannot know whether the +// registered form-field control is a textarea and MDC by default does not have styles +// for textareas in the fill appearance, we add our own minimal textarea styles +// which are scoped to the actual textarea element (i.e. not require a class in the +// text-field wrapper) and integrate better with the any configured appearance. + +// Mixin that can be included to override the default MDC text-field styles +// to properly support textareas. +@mixin _mat-mdc-text-field-textarea-overrides() { + // Ensures that textarea elements inside of the form-field have proper vertical spacing + // to account for the floating label. Also ensures that there is no vertical text overflow. + // TODO(devversion): remove extra specificity if we removed the ":not(outline-appearance)" + // selector from the structure overwrites. Blocked on material-components-web#5326. + .mat-mdc-form-field > .mat-mdc-text-field-wrapper .mat-mdc-form-field-infix + .mat-mdc-textarea-input { + resize: vertical; + box-sizing: border-box; + height: auto; + // Using padding for textareas causes a bad user experience because the text outside + // of the text box will overflow vertically. Also, the vertical spacing for controls + // is set through the infix container to allow for arbitrary form-field controls. + margin: 0; + padding: 0; + border: none; + } + + // By default, MDC aligns the label using percentage. This will be overwritten based + // on whether a textarea is used. This is not possible in our implementation of the + // form-field because we do not know what type of form-field control is set up. Hence + // we always use a fixed position for the label. This does not have any implications. + .mat-mdc-text-field-wrapper .mdc-floating-label { + top: $mat-form-field-baseline; + } + + // In the outline appearance, the textarea needs slightly reduced top spacing because + // the label overflows the outline by 50%. Additionally, horizontal spacing needs to be + // added since the outline is part of the "infix" and we need to account for the outline. + // TODO(devversion): horizontal spacing and extra specificity can be removed once the + // following feature is available: material-components-web#5326. + .mat-mdc-form-field > .mat-mdc-text-field-wrapper.mdc-text-field--outlined + .mat-mdc-form-field-infix .mat-mdc-textarea-input { + margin-top: $mat-form-field-outline-top-spacing; + padding: { + left: $mdc-text-field-input-padding; + right: $mdc-text-field-input-padding; + } + } +} diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss b/src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss new file mode 100644 index 000000000000..2b36a5e3b967 --- /dev/null +++ b/src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss @@ -0,0 +1,63 @@ +@use 'sass:color'; +@use '@material/theme/variables' as theme-variables; + +@import '@material/textfield/variables.import'; + +// Mixin that refreshes the MDC text-field theming variables. This mixin should be used when +// the base MDC theming variables have been explicitly updated, but the component specific +// theming-based variables are still based on the old MDC base theming variables. The mixin +// restores the previous values for the variables to avoid unexpected global side effects. +@mixin _mdc-text-field-refresh-theme-variables() { + $_mdc-text-field-disabled-border-border: $mdc-text-field-disabled-border-border; + $mdc-text-field-disabled-border: rgba(theme-variables.prop-value(on-surface), 0.06) !global; + $_mdc-text-field-bottom-line-idle: $mdc-text-field-bottom-line-idle; + $mdc-text-field-bottom-line-idle: rgba(theme-variables.prop-value(on-surface), 0.42) !global; + $_mdc-text-field-label: $mdc-text-field-label; + $mdc-text-field-label: rgba(theme-variables.prop-value(on-surface), 0.6) !global; + $_mdc-text-field-ink-color: $mdc-text-field-ink-color; + $mdc-text-field-ink-color: rgba(theme-variables.prop-value(on-surface), 0.87) !global; + $_mdc-text-field-focused-label-color: $mdc-text-field-focused-label-color; + $mdc-text-field-focused-label-color: rgba(theme-variables.prop-value(primary), 0.87) !global; + $_mdc-text-field-placeholder-ink-color: $mdc-text-field-placeholder-ink-color; + $mdc-text-field-placeholder-ink-color: rgba(theme-variables.prop-value(on-surface), 0.54) !global; + $_mdc-text-field-disabled-label-color: $mdc-text-field-disabled-label-color; + $mdc-text-field-disabled-label-color: rgba(theme-variables.prop-value(on-surface), 0.38) !global; + $_mdc-text-field-disabled-ink-color: $mdc-text-field-disabled-ink-color; + $mdc-text-field-disabled-ink-color: rgba(theme-variables.prop-value(on-surface), 0.38) !global; + $_mdc-text-field-disabled-placeholder-ink-color: $mdc-text-field-disabled-placeholder-ink-color; + $mdc-text-field-disabled-placeholder-ink-color: + rgba(theme-variables.prop-value(on-surface), 0.38) !global; + $_mdc-text-field-background: $mdc-text-field-background; + $mdc-text-field-background: color.mix( + theme-variables.prop-value(on-surface), theme-variables.prop-value(surface), 4%) !global; + $_mdc-text-field-disabled-background: $mdc-text-field-disabled-background; + $mdc-text-field-disabled-background: color.mix( + theme-variables.prop-value(on-surface), theme-variables.prop-value(surface), 2%) !global; + $_mdc-text-field-outlined-idle-border: $mdc-text-field-outlined-idle-border; + $mdc-text-field-outlined-idle-border: rgba(theme-variables.prop-value(on-surface), 0.38) !global; + $_mdc-text-field-outlined-disabled-border: $mdc-text-field-outlined-disabled-border; + $mdc-text-field-outlined-disabled-border: + rgba(theme-variables.prop-value(on-surface), 0.06) !global; + $_mdc-text-field-outlined-hover-border: $mdc-text-field-outlined-hover-border; + $mdc-text-field-outlined-hover-border: rgba(theme-variables.prop-value(on-surface), 0.87) !global; + + // The content will be generated with the refreshed MDC text-field theming variables. + @content; + + // Reset all variables to ensure that this mixin does not cause unexpected side effects. + $mdc-text-field-disabled-border-border: $_mdc-text-field-disabled-border-border !global; + $mdc-text-field-bottom-line-idle: $_mdc-text-field-bottom-line-idle !global; + $mdc-text-field-label: $_mdc-text-field-label !global; + $mdc-text-field-ink-color: $_mdc-text-field-ink-color !global; + $mdc-text-field-focused-label-color: $_mdc-text-field-focused-label-color !global; + $mdc-text-field-placeholder-ink-color: $_mdc-text-field-placeholder-ink-color !global; + $mdc-text-field-disabled-label-color: $_mdc-text-field-disabled-label-color !global; + $mdc-text-field-disabled-ink-color: $_mdc-text-field-disabled-ink-color !global; + $mdc-text-field-disabled-placeholder-ink-color: + $_mdc-text-field-disabled-placeholder-ink-color !global; + $mdc-text-field-background: $_mdc-text-field-background !global; + $mdc-text-field-disabled-background: $_mdc-text-field-disabled-background !global; + $mdc-text-field-outlined-idle-border: $_mdc-text-field-outlined-idle-border !global; + $mdc-text-field-outlined-disabled-border: $_mdc-text-field-outlined-disabled-border !global; + $mdc-text-field-outlined-hover-border: $_mdc-text-field-outlined-hover-border !global; +} diff --git a/src/material-experimental/mdc-form-field/directives/error.ts b/src/material-experimental/mdc-form-field/directives/error.ts new file mode 100644 index 000000000000..1dc62bab0b43 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/error.ts @@ -0,0 +1,24 @@ +/** + * @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 {Directive, Input} from '@angular/core'; + +let nextUniqueId = 0; + +/** Single error message to be shown underneath the form-field. */ +@Directive({ + selector: 'mat-error', + host: { + 'class': 'mat-mdc-form-field-error', + 'role': 'alert', + '[id]': 'id', + } +}) +export class MatError { + @Input() id: string = `mat-error-${nextUniqueId++}`; +} diff --git a/src/material-experimental/mdc-form-field/directives/floating-label.ts b/src/material-experimental/mdc-form-field/directives/floating-label.ts new file mode 100644 index 000000000000..c9d091154c35 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/floating-label.ts @@ -0,0 +1,45 @@ +/** + * @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 {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; +import {MDCFloatingLabel} from '@material/floating-label'; + +/** + * Internal directive that creates an instance of the MDC floating label + * component. Using a directive allows us to conditionally render a floating label + * in the template without having to manually instantiate the `MDCFloatingLabel` component. + * + * The component is responsible for setting up the floating label styles, and for providing + * an @Input that can be used by the form-field to toggle floating state of the label. + */ +@Directive({ + selector: 'label[matFormFieldFloatingLabel]', + host: { + 'class': 'mdc-floating-label', + }, +}) +export class MatFormFieldFloatingLabel extends MDCFloatingLabel implements OnDestroy { + + @Input() + get floating() { return this._floating; } + set floating(shouldFloat: boolean) { + if (shouldFloat !== this._floating) { + this._floating = shouldFloat; + this.float(shouldFloat); + } + } + private _floating = false; + + constructor(elementRef: ElementRef) { + super(elementRef.nativeElement); + } + + ngOnDestroy() { + this.destroy(); + } +} diff --git a/src/material-experimental/mdc-form-field/directives/hint.ts b/src/material-experimental/mdc-form-field/directives/hint.ts new file mode 100644 index 000000000000..073a93d4b2dc --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/hint.ts @@ -0,0 +1,30 @@ +/** + * @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 {Directive, Input} from '@angular/core'; + +let nextUniqueId = 0; + +/** Hint text to be shown underneath the form field control. */ +@Directive({ + selector: 'mat-hint', + host: { + 'class': 'mat-mdc-form-field-hint', + '[class.mat-form-field-hint-end]': 'align == "end"', + '[id]': 'id', + // Remove align attribute to prevent it from interfering with layout. + '[attr.align]': 'null', + } +}) +export class MatHint { + /** Whether to align the hint label at the start or end of the line. */ + @Input() align: 'start' | 'end' = 'start'; + + /** Unique ID for the hint. Used for the aria-describedby on the form field control. */ + @Input() id: string = `mat-hint-${nextUniqueId++}`; +} diff --git a/src/material-experimental/mdc-form-field/directives/label.ts b/src/material-experimental/mdc-form-field/directives/label.ts new file mode 100644 index 000000000000..3801266ca371 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/label.ts @@ -0,0 +1,16 @@ +/** + * @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 {Directive} from '@angular/core'; + +/** The floating label for a `mat-form-field`. */ +@Directive({ + selector: 'mat-label', +}) +export class MatLabel { +} diff --git a/src/material-experimental/mdc-form-field/directives/line-ripple.ts b/src/material-experimental/mdc-form-field/directives/line-ripple.ts new file mode 100644 index 000000000000..98a048ac4dff --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/line-ripple.ts @@ -0,0 +1,34 @@ +/** + * @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 {Directive, ElementRef, OnDestroy} from '@angular/core'; +import {MDCLineRipple} from '@material/line-ripple'; + +/** + * Internal directive that creates an instance of the MDC line-ripple component. Using a + * directive allows us to conditionally render a line-ripple in the template without having + * to manually create and destroy the `MDCLineRipple` component whenever the condition changes. + * + * The directive sets up the styles for the line-ripple and provides an API for activating + * and deactivating the line-ripple. + */ +@Directive({ + selector: 'div[matFormFieldLineRipple]', + host: { + 'class': 'mdc-line-ripple', + }, +}) +export class MatFormFieldLineRipple extends MDCLineRipple implements OnDestroy { + constructor(elementRef: ElementRef) { + super(elementRef.nativeElement); + } + + ngOnDestroy() { + this.destroy(); + } +} diff --git a/src/material-experimental/mdc-form-field/directives/notched-outline.html b/src/material-experimental/mdc-form-field/directives/notched-outline.html new file mode 100644 index 000000000000..08b287042c07 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/notched-outline.html @@ -0,0 +1,5 @@ +
    +
    + +
    +
    diff --git a/src/material-experimental/mdc-form-field/directives/notched-outline.ts b/src/material-experimental/mdc-form-field/directives/notched-outline.ts new file mode 100644 index 000000000000..011b1f81d3f2 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/notched-outline.ts @@ -0,0 +1,76 @@ +/** + * @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 {Platform} from '@angular/cdk/platform'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, OnDestroy, + ViewEncapsulation +} from '@angular/core'; +import {MDCNotchedOutline} from '@material/notched-outline'; + +/** + * Internal directive that creates an instance of the MDC notched-outline component. Using + * a directive allows us to conditionally render a notched-outline in the template without + * having to manually create and destroy the `MDCNotchedOutline` component whenever the + * appearance changes. + * + * The directive sets up the HTML structure and styles for the notched-outline, but also + * exposes a programmatic API to toggle the state of the notch. + */ +@Component({ + selector: 'div[matFormFieldNotchedOutline]', + templateUrl: './notched-outline.html', + host: { + 'class': 'mdc-notched-outline', + }, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class MatFormFieldNotchedOutline implements AfterViewInit, OnDestroy { + private _mdcNotchedOutline: MDCNotchedOutline|null = null; + + constructor(private _elementRef: ElementRef, private _platform: Platform) {} + + ngAfterViewInit() { + // The notched outline cannot be attached in the server platform. It schedules tasks + // for the next browser animation frame and relies on element client rectangles to render + // the outline notch. To avoid failures on the server, we just do not initialize it, + // but the actual notched-outline styles will be still displayed. + if (this._platform.isBrowser) { + // The notch component relies on the view to be initialized. This means + // that we cannot extend from the "MDCNotchedOutline". + this._mdcNotchedOutline = MDCNotchedOutline.attachTo(this._elementRef.nativeElement); + } + } + + ngOnDestroy() { + if (this._mdcNotchedOutline !== null) { + this._mdcNotchedOutline.destroy(); + } + } + + /** + * Updates classes and styles to open the notch to the specified width. + * @param notchWidth The notch width in the outline. + */ + notch(notchWidth: number) { + if (this._mdcNotchedOutline !== null) { + this._mdcNotchedOutline.notch(notchWidth); + } + } + + /** Closes the notch. */ + closeNotch() { + if (this._mdcNotchedOutline !== null) { + this._mdcNotchedOutline.closeNotch(); + } + } +} diff --git a/src/material-experimental/mdc-form-field/directives/prefix.ts b/src/material-experimental/mdc-form-field/directives/prefix.ts new file mode 100644 index 000000000000..8c5ee9da0ca7 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/prefix.ts @@ -0,0 +1,15 @@ +/** + * @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 {Directive} from '@angular/core'; + +/** Prefix to be placed in front of the form field. */ +@Directive({ + selector: '[matPrefix]', +}) +export class MatPrefix {} diff --git a/src/material-experimental/mdc-form-field/directives/suffix.ts b/src/material-experimental/mdc-form-field/directives/suffix.ts new file mode 100644 index 000000000000..9e61eec01cf9 --- /dev/null +++ b/src/material-experimental/mdc-form-field/directives/suffix.ts @@ -0,0 +1,15 @@ +/** + * @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 {Directive} from '@angular/core'; + +/** Suffix to be placed at the end of the form field. */ +@Directive({ + selector: '[matSuffix]', +}) +export class MatSuffix {} diff --git a/src/material-experimental/mdc-form-field/form-field.e2e.spec.ts b/src/material-experimental/mdc-form-field/form-field.e2e.spec.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/material-experimental/mdc-form-field/form-field.html b/src/material-experimental/mdc-form-field/form-field.html new file mode 100644 index 000000000000..854de64c1267 --- /dev/null +++ b/src/material-experimental/mdc-form-field/form-field.html @@ -0,0 +1,69 @@ + + + + + +
    +
    +
    + +
    + +
    + + + + + + +
    + +
    + + +
    + +
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    + {{hintLabel}} + +
    + +
    +
    diff --git a/src/material-experimental/mdc-form-field/form-field.scss b/src/material-experimental/mdc-form-field/form-field.scss new file mode 100644 index 000000000000..0e500b9b9335 --- /dev/null +++ b/src/material-experimental/mdc-form-field/form-field.scss @@ -0,0 +1,54 @@ +@import '@material/textfield/mixins.import'; + +@import 'form-field-sizing'; +@import 'form-field-subscript'; +@import 'form-field-bottom-line'; +@import 'mdc-text-field-textarea-overrides'; +@import 'mdc-text-field-structure-overrides'; + +// Base styles for MDC notched-outline, MDC floating label and MDC text-field. +@include mdc-notched-outline-core-styles($query: $mat-base-styles-query); +@include mdc-floating-label-core-styles($query: $mat-base-styles-query); +@include mdc-text-field-core-styles($query: $mat-base-styles-query); + +// MDC text-field overwrites. +@include _mat-mdc-text-field-textarea-overrides(); +@include _mat-mdc-text-field-structure-overrides(); + +// Include the subscript and bottom-line styles. +@include _mat-form-field-subscript(); +@include _mat-form-field-bottom-line(); + +// Host element of the form-field. It contains the mdc-text-field wrapper +// and the subscript wrapper. +.mat-mdc-form-field { + display: inline-flex; + // this container contains the text-field and the subscript. The subscript + // should be displayed below the text-field. Hence the column direction. + flex-direction: column; +} + +// Container that contains the prefixes, infix and suffixes. These elements should +// be aligned vertically in the baseline and in a single row. +.mat-mdc-form-field-flex { + display: inline-flex; + align-items: baseline; + box-sizing: border-box; + width: 100%; +} + +// Infix that contains the projected content (usually an input or a textarea). We ensure +// that the projected form-field control and content can stretch as needed, but we also +// apply a default infix width to make the form-field's look natural. +.mat-mdc-form-field-infix { + flex: auto; + min-width: 0; + width: $mat-form-field-default-infix-width; + // Needed so that the floating label does not overlap with prefixes or suffixes. + position: relative; + + // We add a minimum height in order to ensure that custom controls have the same + // default vertical space as text-field inputs (with respect to the vertical padding). + min-height: $mdc-text-field-height; + box-sizing: border-box; +} diff --git a/src/material-experimental/mdc-form-field/form-field.ts b/src/material-experimental/mdc-form-field/form-field.ts new file mode 100644 index 000000000000..b370d7ad1b66 --- /dev/null +++ b/src/material-experimental/mdc-form-field/form-field.ts @@ -0,0 +1,504 @@ +/** + * @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 { + AfterContentChecked, + AfterContentInit, + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + ContentChildren, + ElementRef, + Inject, InjectionToken, + Input, + isDevMode, + OnDestroy, + Optional, + QueryList, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import {NgControl} from '@angular/forms'; +import { + LabelOptions, + MAT_LABEL_GLOBAL_OPTIONS, + ThemePalette +} from '@angular/material/core'; +import { + getMatFormFieldDuplicatedHintError, + getMatFormFieldMissingControlError, + MatFormField as NonMdcFormField, + matFormFieldAnimations, + MatFormFieldControl, +} from '@angular/material/form-field'; +import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; +import {MDCTextFieldAdapter, MDCTextFieldFoundation} from '@material/textfield'; +import {Subject} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; +import {MatError} from './directives/error'; +import {MatFormFieldFloatingLabel} from './directives/floating-label'; +import {MatHint} from './directives/hint'; +import {MatLabel} from './directives/label'; +import {MatFormFieldLineRipple} from './directives/line-ripple'; +import {MatFormFieldNotchedOutline} from './directives/notched-outline'; +import {MatPrefix} from './directives/prefix'; +import {MatSuffix} from './directives/suffix'; + +/** Type for the available floatLabel values. */ +export type FloatLabelType = 'always' | 'auto'; + +/** Possible appearance styles for the form field. */ +export type MatFormFieldAppearance = 'fill' | 'outline'; + +/** + * Represents the default options for the form field that can be configured + * using the `MAT_FORM_FIELD_DEFAULT_OPTIONS` injection token. + */ +export interface MatFormFieldDefaultOptions { + appearance?: MatFormFieldAppearance; + hideRequiredMarker?: boolean; +} + +/** + * Injection token that can be used to configure the + * default options for all form field within an app. + */ +export const MAT_FORM_FIELD_DEFAULT_OPTIONS = + new InjectionToken('MAT_FORM_FIELD_DEFAULT_OPTIONS'); + +let nextUniqueId = 0; + +/** Default appearance used by the form-field. */ +const DEFAULT_APPEARANCE: MatFormFieldAppearance = 'fill'; + +/** Default appearance used by the form-field. */ +const DEFAULT_FLOAT_LABEL: FloatLabelType = 'auto'; + +/** Container for form controls that applies Material Design styling and behavior. */ +@Component({ + selector: 'mat-form-field', + exportAs: 'matFormField', + templateUrl: './form-field.html', + styleUrls: ['./form-field.css'], + animations: [matFormFieldAnimations.transitionMessages], + host: { + 'class': 'mat-mdc-form-field', + '[class.mat-mdc-form-field-label-always-float]': '_shouldAlwaysFloat()', + '[class.mat-form-field-invalid]': '_control.errorState', + '[class.mat-form-field-disabled]': '_control.disabled', + '[class.mat-form-field-autofilled]': '_control.autofilled', + '[class.mat-focused]': '_control.focused', + '[class.mat-accent]': 'color == "accent"', + '[class.mat-warn]': 'color == "warn"', + '[class.ng-untouched]': '_shouldForward("untouched")', + '[class.ng-touched]': '_shouldForward("touched")', + '[class.ng-pristine]': '_shouldForward("pristine")', + '[class.ng-dirty]': '_shouldForward("dirty")', + '[class.ng-valid]': '_shouldForward("valid")', + '[class.ng-invalid]': '_shouldForward("invalid")', + '[class.ng-pending]': '_shouldForward("pending")', + '[class._mat-animation-noopable]': '!_animationsEnabled', + }, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + // Temporary workaround that allows us to test the MDC form-field against + // components which inject the non-mdc form-field (e.g. autocomplete). + {provide: NonMdcFormField, useExisting: MatFormField} + ] +}) +export class MatFormField implements AfterViewInit, OnDestroy, AfterContentChecked, + AfterContentInit { + @ViewChild('textField') _textField: ElementRef; + @ViewChild(MatFormFieldFloatingLabel) _floatingLabel: MatFormFieldFloatingLabel|undefined; + @ViewChild(MatFormFieldNotchedOutline) _notchedOutline: MatFormFieldNotchedOutline|undefined; + @ViewChild(MatFormFieldLineRipple) _lineRipple: MatFormFieldLineRipple|undefined; + + @ContentChild(MatLabel) _labelChildNonStatic: MatLabel|undefined; + @ContentChild(MatLabel, {static: true}) _labelChildStatic: MatLabel|undefined; + @ContentChild(MatFormFieldControl) _formFieldControl: MatFormFieldControl; + @ContentChildren(MatPrefix, {descendants: true}) _prefixChildren: QueryList; + @ContentChildren(MatSuffix, {descendants: true}) _suffixChildren: QueryList; + @ContentChildren(MatError, {descendants: true}) _errorChildren: QueryList; + @ContentChildren(MatHint, {descendants: true}) _hintChildren: QueryList; + + /** Whether the required marker should be hidden. */ + @Input() hideRequiredMarker: boolean = false; + + /** The color palette for the form-field. */ + @Input() color: ThemePalette = 'primary'; + + /** Whether the label should always float or float as the user types. */ + @Input() + get floatLabel(): FloatLabelType { + return this._floatLabel || (this._labelOptions && this._labelOptions.float) + || DEFAULT_FLOAT_LABEL; + } + set floatLabel(value: FloatLabelType) { + if (value !== this._floatLabel) { + this._floatLabel = value; + // For backwards compatibility. Custom form-field controls or directives might set + // the "floatLabel" input and expect the form-field view to be updated automatically. + // e.g. autocomplete trigger. Ideally we'd get rid of this and the consumers would just + // emit the "stateChanges" observable. TODO(devversion): consider removing. + this._changeDetectorRef.markForCheck(); + } + } + private _floatLabel: FloatLabelType; + + /** The form-field appearance style. */ + @Input() + get appearance(): MatFormFieldAppearance { return this._appearance; } + set appearance(value: MatFormFieldAppearance) { + this._appearance = value || (this._defaults && this._defaults.appearance) || DEFAULT_APPEARANCE; + } + private _appearance: MatFormFieldAppearance = DEFAULT_APPEARANCE; + + /** Text for the form field hint. */ + @Input() + get hintLabel(): string { return this._hintLabel; } + set hintLabel(value: string) { + this._hintLabel = value; + this._processHints(); + } + private _hintLabel = ''; + + // Unique id for the hint label. + _hintLabelId: string = `mat-hint-${nextUniqueId++}`; + + // Unique id for the internal form field label. + _labelId = `mat-form-field-label-${nextUniqueId++}`; + + /** Whether the Angular animations are enabled. */ + _animationsEnabled: boolean; + + /** State of the mat-hint and mat-error animations. */ + _subscriptAnimationState: string = ''; + + /** Gets the current form field control */ + get _control(): MatFormFieldControl { + return this._explicitFormFieldControl || this._formFieldControl; + } + set _control(value) { this._explicitFormFieldControl = value; } + + private _destroyed = new Subject(); + private _isFocused: boolean|null = null; + private _explicitFormFieldControl: MatFormFieldControl; + private _foundation: MDCTextFieldFoundation; + private _adapter: MDCTextFieldAdapter = { + addClass: className => this._textField.nativeElement.classList.add(className), + removeClass: className => this._textField.nativeElement.classList.remove(className), + hasClass: className => this._textField.nativeElement.classList.contains(className), + + hasLabel: () => this._hasFloatingLabel(), + isFocused: () => this._control.focused, + hasOutline: () => this._hasOutline(), + + // MDC text-field will call this method on focus, blur and value change. It expects us + // to update the floating label state accordingly. Though we make this a noop because we + // want to react to floating label state changes through change detection. Relying on this + // adapter method would mean that the label would not update if the custom form-field control + // sets "shouldLabelFloat" to true, or if the "floatLabel" input binding changes to "always". + floatLabel: () => {}, + + // Label shaking is not supported yet. It will require a new API for form field + // controls to trigger the shaking. This can be a feature in the future. + // TODO(devversion): explore options on how to integrate label shaking. + shakeLabel: () => {}, + + getLabelWidth: () => this._floatingLabel ? this._floatingLabel.getWidth() : 0, + notchOutline: labelWidth => this._notchedOutline && this._notchedOutline.notch(labelWidth), + closeOutline: () => this._notchedOutline && this._notchedOutline.closeNotch(), + + activateLineRipple: () => this._lineRipple && this._lineRipple.activate(), + deactivateLineRipple: () => this._lineRipple && this._lineRipple.deactivate(), + + // The foundation tries to register events on the input. This is not matching + // our concept of abstract form field controls. We handle each event manually + // in "ngDoCheck" based on the form-field control state. The following events + // need to be handled: focus, blur. We do not handle the "input" event since + // that one is only needed for the text-field character count, which we do + // not implement as part of the form-field, but should be implemented manually + // by consumers using template bindings. + registerInputInteractionHandler: () => {}, + deregisterInputInteractionHandler: () => {}, + + // We do not have a reference to the native input since we work with abstract form field + // controls. MDC needs a reference to the native input optionally to handle character + // counting and value updating. These are both things we do not handle from within the + // form-field, so we can just return null. + getNativeInput: () => null, + + // This method will never be called since we do not have the ability to add event listeners + // to the native input. This is because the form control is not necessarily an input, and + // the form field deals with abstract form controls of any type. + setLineRippleTransformOrigin: () => {}, + + // The foundation tries to register click and keyboard events on the form-field to figure out + // if the input value changes through user interaction. Based on that, the foundation tries + // to focus the input. Since we do not handle the input value as part of the form-field, nor + // it's guaranteed to be an input (see adapter methods above), this is a noop. + deregisterTextFieldInteractionHandler: () => {}, + registerTextFieldInteractionHandler: () => {}, + + // The foundation tries to setup a "MutationObserver" in order to watch for attributes + // like "maxlength" or "pattern" to change. The foundation will update the validity state + // based on that. We do not need this logic since we handle the validity through the + // abstract form control instance. + deregisterValidationAttributeChangeHandler: () => {}, + registerValidationAttributeChangeHandler: () => null as any, + }; + + constructor(private _elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef, + @Optional() @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) + private _defaults?: MatFormFieldDefaultOptions, + @Optional() @Inject(MAT_LABEL_GLOBAL_OPTIONS) private _labelOptions?: LabelOptions, + @Optional() @Inject(ANIMATION_MODULE_TYPE) _animationMode?: string) { + this._animationsEnabled = _animationMode !== 'NoopAnimations'; + + if (_defaults && _defaults.appearance) { + this.appearance = _defaults.appearance; + } else if (_defaults && _defaults.hideRequiredMarker) { + this.hideRequiredMarker = true; + } + } + + ngAfterViewInit() { + this._foundation = new MDCTextFieldFoundation(this._adapter); + + // MDC uses the "shouldFloat" getter to know whether the label is currently floating. This + // does not match our implementation of when the label floats because we support more cases. + // For example, consumers can set "@Input floatLabel" to always, or the custom form-field + // control can set "MatFormFieldControl#shouldLabelFloat" to true. To ensure that MDC knows + // when the label is floating, we overwrite the property to be based on the method we use to + // determine the current state of the floating label. + Object.defineProperty(this._foundation, 'shouldFloat', { + get: () => this._shouldLabelFloat(), + }); + + // By default, the foundation determines the validity of the text-field from the + // specified native input. Since we don't pass a native input to the foundation because + // abstract form controls are not necessarily consisting of an input, we handle the + // text-field validity through the abstract form-field control state. + this._foundation.isValid = () => !this._control.errorState; + + // Initial focus state sync. This happens rarely, but we want to account for + // it in case the form-field control has "focused" set to true on init. + this._updateFocusState(); + // Initial notch update since we overwrote the "shouldFloat" getter. + this._rerenderOutlineNotch(); + // Enable animations now. This ensures we don't animate on initial render. + this._subscriptAnimationState = 'enter'; + } + + ngAfterContentInit() { + this._assertFormFieldControl(); + this._initializeControl(); + this._initializeSubscript(); + } + + ngAfterContentChecked() { + this._assertFormFieldControl(); + } + + ngOnDestroy() { + this._destroyed.next(); + this._destroyed.complete(); + } + + /** + * Gets an ElementRef for the element that a overlay attached to the form-field + * should be positioned relative to. + */ + getConnectedOverlayOrigin(): ElementRef { + return this._textField || this._elementRef; + } + + /** Animates the placeholder up and locks it in position. */ + _animateAndLockLabel(): void { + // This is for backwards compatibility only. Consumers of the form-field might use + // this method. e.g. the autocomplete trigger. This method has been added to the non-MDC + // form-field because setting "floatLabel" to "always" caused the label to float without + // animation. This is different in MDC where the label always animates, so this method + // is no longer necessary. There doesn't seem any benefit in adding logic to allow changing + // the floating label state without animations. The non-MDC implementation was inconsistent + // because it always animates if "floatLabel" is set away from "always". + // TODO(devversion): consider removing this method when releasing the MDC form-field. + if (this._hasFloatingLabel()) { + this.floatLabel = 'always'; + } + } + + /** Initializes the registered form-field control. */ + private _initializeControl() { + const control = this._control; + + if (control.controlType) { + this._elementRef.nativeElement.classList.add( + `mat-mdc-form-field-type-${control.controlType}`); + } + + // Subscribe to changes in the child control state in order to update the form field UI. + control.stateChanges.subscribe(() => { + this._updateFocusState(); + this._syncDescribedByIds(); + this._changeDetectorRef.markForCheck(); + }); + + // Run change detection if the value changes. + if (control.ngControl && control.ngControl.valueChanges) { + control.ngControl.valueChanges + .pipe(takeUntil(this._destroyed)) + .subscribe(() => this._changeDetectorRef.markForCheck()); + } + } + + /** + * Initializes the subscript by validating hints and synchronizing "aria-describedby" ids + * with the custom form-field control. Also subscribes to hint and error changes in order + * to be able to validate and synchronize ids on change. + */ + private _initializeSubscript() { + // Re-validate when the number of hints changes. + this._hintChildren.changes.subscribe(() => { + this._processHints(); + this._changeDetectorRef.markForCheck(); + }); + + // Update the aria-described by when the number of errors changes. + this._errorChildren.changes.subscribe(() => { + this._syncDescribedByIds(); + this._changeDetectorRef.markForCheck(); + }); + + // Initial mat-hint validation and subscript describedByIds sync. + this._validateHints(); + this._syncDescribedByIds(); + } + + /** Throws an error if the form field's control is missing. */ + private _assertFormFieldControl() { + if (!this._control) { + throw getMatFormFieldMissingControlError(); + } + } + private _updateFocusState() { + // Usually the MDC foundation would call "activateFocus" and "deactivateFocus" whenever + // certain DOM events are emitted. This is not possible in our implementation of the + // form-field because we support abstract form field controls which are not necessarily + // of type input, nor do we have a reference to a native form-field control element. Instead + // we handle the focus by checking if the abstract form-field control focused state changes. + if (this._control.focused && !this._isFocused) { + this._isFocused = true; + this._foundation.activateFocus(); + } else if (!this._control.focused && (this._isFocused || this._isFocused === null)) { + this._isFocused = false; + this._foundation.deactivateFocus(); + } + } + + _rerenderOutlineNotch() { + if (this._floatingLabel && this._hasOutline()) { + this._foundation.notchOutline(this._shouldLabelFloat()); + } + } + + /** Whether the floating label should always float or not. */ + _shouldAlwaysFloat() { + return this.floatLabel === 'always'; + } + + _hasOutline() { + return this.appearance === 'outline'; + } + + _hasFloatingLabel() { + return !!this._labelChildNonStatic || !!this._labelChildStatic; + } + + _shouldLabelFloat() { + return this._control.shouldLabelFloat || this._shouldAlwaysFloat(); + } + + /** Determines whether a class from the NgControl should be forwarded to the host element. */ + _shouldForward(prop: keyof NgControl): boolean { + const ngControl = this._control ? this._control.ngControl : null; + return ngControl && ngControl[prop]; + } + + /** Determines whether to display hints or errors. */ + _getDisplayedMessages(): 'error' | 'hint' { + return (this._errorChildren && this._errorChildren.length > 0 && + this._control.errorState) ? 'error' : 'hint'; + } + + /** Does any extra processing that is required when handling the hints. */ + private _processHints() { + this._validateHints(); + this._syncDescribedByIds(); + } + + /** + * Ensure that there is a maximum of one of each "mat-hint" alignment specified. The hint + * label specified set through the input is being considered as "start" aligned. + * + * This method is a noop if Angular runs in production mode. + */ + private _validateHints() { + if (isDevMode() && this._hintChildren) { + let startHint: MatHint; + let endHint: MatHint; + this._hintChildren.forEach((hint: MatHint) => { + if (hint.align === 'start') { + if (startHint || this.hintLabel) { + throw getMatFormFieldDuplicatedHintError('start'); + } + startHint = hint; + } else if (hint.align === 'end') { + if (endHint) { + throw getMatFormFieldDuplicatedHintError('end'); + } + endHint = hint; + } + }); + } + } + + /** + * Sets the list of element IDs that describe the child control. This allows the control to update + * its `aria-describedby` attribute accordingly. + */ + private _syncDescribedByIds() { + if (this._control) { + let ids: string[] = []; + + if (this._getDisplayedMessages() === 'hint') { + const startHint = this._hintChildren ? + this._hintChildren.find(hint => hint.align === 'start') : null; + const endHint = this._hintChildren ? + this._hintChildren.find(hint => hint.align === 'end') : null; + + if (startHint) { + ids.push(startHint.id); + } else if (this._hintLabel) { + ids.push(this._hintLabelId); + } + + if (endHint) { + ids.push(endHint.id); + } + } else if (this._errorChildren) { + ids = this._errorChildren.map(error => error.id); + } + + this._control.setDescribedByIds(ids); + } + } +} diff --git a/src/material-experimental/mdc-helpers/index.ts b/src/material-experimental/mdc-form-field/index.ts similarity index 100% rename from src/material-experimental/mdc-helpers/index.ts rename to src/material-experimental/mdc-form-field/index.ts diff --git a/src/material-experimental/mdc-form-field/module.ts b/src/material-experimental/mdc-form-field/module.ts new file mode 100644 index 000000000000..6b363af8b1b5 --- /dev/null +++ b/src/material-experimental/mdc-form-field/module.ts @@ -0,0 +1,51 @@ +/** + * @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 {ObserversModule} from '@angular/cdk/observers'; +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; +import {MatError} from './directives/error'; +import {MatFormFieldFloatingLabel} from './directives/floating-label'; +import {MatHint} from './directives/hint'; +import {MatLabel} from './directives/label'; +import {MatFormFieldLineRipple} from './directives/line-ripple'; +import {MatFormFieldNotchedOutline} from './directives/notched-outline'; +import {MatPrefix} from './directives/prefix'; +import {MatSuffix} from './directives/suffix'; +import {MatFormField} from './form-field'; + +@NgModule({ + imports: [ + MatCommonModule, + CommonModule, + ObserversModule + ], + exports: [ + MatFormField, + MatLabel, + MatHint, + MatError, + MatPrefix, + MatSuffix, + MatCommonModule + ], + declarations: [ + MatFormField, + MatLabel, + MatError, + MatHint, + MatPrefix, + MatSuffix, + MatFormFieldFloatingLabel, + MatFormFieldNotchedOutline, + MatFormFieldLineRipple + ], +}) +export class MatFormFieldModule { +} diff --git a/src/material-experimental/mdc-form-field/public-api.ts b/src/material-experimental/mdc-form-field/public-api.ts new file mode 100644 index 000000000000..81be8096f147 --- /dev/null +++ b/src/material-experimental/mdc-form-field/public-api.ts @@ -0,0 +1,21 @@ +/** + * @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 { + MatFormFieldControl, + getMatFormFieldDuplicatedHintError, + getMatFormFieldMissingControlError, +} from '@angular/material/form-field'; + +export * from './directives/label'; +export * from './directives/error'; +export * from './directives/hint'; +export * from './directives/prefix'; +export * from './directives/suffix'; +export * from './form-field'; +export * from './module'; diff --git a/src/material-experimental/mdc-form-field/testing/BUILD.bazel b/src/material-experimental/mdc-form-field/testing/BUILD.bazel new file mode 100644 index 000000000000..83718c20e105 --- /dev/null +++ b/src/material-experimental/mdc-form-field/testing/BUILD.bazel @@ -0,0 +1,59 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material-experimental/mdc-form-field/testing", + deps = [ + "//src/cdk/testing", + "//src/material/form-field/testing", + "//src/material/form-field/testing/control", + "//src/material/input/testing", + "//src/material/select/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["shared.spec.ts"], + ), + deps = [ + ":testing", + "//src/cdk/overlay", + "//src/material-experimental/mdc-form-field", + "//src/material-experimental/mdc-input", + "//src/material/autocomplete", + "//src/material/core", + "//src/material/form-field/testing:harness_tests_lib", + "//src/material/input/testing", + "//src/material/select", + "//src/material/select/testing", + "@npm//@angular/common", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = [ + "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", + "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", + "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", + "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", + ], + deps = [ + ":unit_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) diff --git a/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts b/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts new file mode 100644 index 000000000000..d03a84797556 --- /dev/null +++ b/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts @@ -0,0 +1,38 @@ +import {OverlayModule} from '@angular/cdk/overlay'; +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatAutocompleteModule} from '@angular/material/autocomplete'; +import {MatCommonModule, MatOptionModule} from '@angular/material/core'; +import {MatInputHarness} from '@angular/material/input/testing'; +import { + MAT_SELECT_SCROLL_STRATEGY_PROVIDER, + MatSelect, + MatSelectTrigger +} from '@angular/material/select'; +import {MatSelectHarness} from '@angular/material/select/testing'; +import {runHarnessTests} from '@angular/material/form-field/testing/shared.spec'; +import {MatFormFieldHarness} from './form-field-harness'; + +// TODO: remove this once there is a `MatSelect` module which does not come +// with the form-field module provided. This is a copy of the `MatSelect` module +// that does not provide any form-field module. +@NgModule({ + imports: [CommonModule, OverlayModule, MatOptionModule, MatCommonModule], + exports: [MatSelect, MatSelectTrigger, MatOptionModule, MatCommonModule], + declarations: [MatSelect, MatSelectTrigger], + providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER] +}) +export class SelectWithoutFormFieldModule { +} + +describe('MDC-based MatFormFieldHarness', () => { + runHarnessTests( + [MatFormFieldModule, MatAutocompleteModule, MatInputModule, SelectWithoutFormFieldModule], { + formFieldHarness: MatFormFieldHarness as any, + inputHarness: MatInputHarness, + selectHarness: MatSelectHarness, + isMdcImplementation: true, + }); +}); diff --git a/src/material-experimental/mdc-form-field/testing/form-field-harness.ts b/src/material-experimental/mdc-form-field/testing/form-field-harness.ts new file mode 100644 index 000000000000..d20ceaed7c56 --- /dev/null +++ b/src/material-experimental/mdc-form-field/testing/form-field-harness.ts @@ -0,0 +1,230 @@ +/** + * @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 { + ComponentHarness, + ComponentHarnessConstructor, + HarnessPredicate, + HarnessQuery, + TestElement +} from '@angular/cdk/testing'; +import {FormFieldHarnessFilters} from '@angular/material/form-field/testing'; +import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control'; +import {MatInputHarness} from '@angular/material/input/testing'; +import {MatSelectHarness} from '@angular/material/select/testing'; + +// TODO(devversion): support datepicker harness once developed (COMP-203). +// Also support chip list harness. +/** Possible harnesses of controls which can be bound to a form-field. */ +export type FormFieldControlHarness = MatInputHarness|MatSelectHarness; + +/** Harness for interacting with a MDC-based form-field's in tests. */ +export class MatFormFieldHarness extends ComponentHarness { + static hostSelector = '.mat-mdc-form-field'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatFormFieldHarness` that meets + * certain criteria. + * @param options Options for filtering which form field instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: FormFieldHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatFormFieldHarness, options) + .addOption( + 'floatingLabelText', options.floatingLabelText, + async (harness, text) => HarnessPredicate.stringMatches(await harness.getLabel(), text)) + .addOption( + 'hasErrors', options.hasErrors, + async (harness, hasErrors) => await harness.hasErrors() === hasErrors); + } + + private _mdcTextField = this.locatorFor('.mat-mdc-text-field-wrapper'); + + private _prefixContainer = this.locatorForOptional('.mat-mdc-form-field-prefix'); + private _suffixContainer = this.locatorForOptional('.mat-mdc-form-field-suffix'); + private _label = this.locatorForOptional('.mdc-floating-label'); + private _errors = this.locatorForAll('.mat-mdc-form-field-error'); + private _hints = this.locatorForAll('.mat-mdc-form-field-hint'); + + private _inputControl = this.locatorForOptional(MatInputHarness); + private _selectControl = this.locatorForOptional(MatSelectHarness); + + /** Gets the appearance of the form-field. */ + async getAppearance(): Promise<'fill'|'outline'> { + const textFieldEl = await this._mdcTextField(); + if (await textFieldEl.hasClass('mdc-text-field--outlined')) { + return 'outline'; + } + return 'fill'; + } + + /** + * Gets the harness of the control that is bound to the form-field. Only + * default controls such as "MatInputHarness" and "MatSelectHarness" are + * supported. + */ + async getControl(): Promise; + + /** + * Gets the harness of the control that is bound to the form-field. Searches + * for a control that matches the specified harness type. + */ + async getControl(type: ComponentHarnessConstructor): + Promise; + + /** + * Gets the harness of the control that is bound to the form-field. Searches + * for a control that matches the specified harness predicate. + */ + async getControl(type: HarnessPredicate): + Promise; + + // Implementation of the "getControl" method overload signatures. + async getControl(type?: HarnessQuery) { + if (type) { + return this.locatorForOptional(type)(); + } + const hostEl = await this.host(); + const [isInput, isSelect] = await Promise.all([ + hostEl.hasClass('mat-mdc-form-field-type-mat-input'), + hostEl.hasClass('mat-mdc-form-field-type-mat-select'), + ]); + if (isInput) { + return this._inputControl(); + } else if (isSelect) { + return this._selectControl(); + } + return null; + } + + /** Whether the form-field has a label. */ + async hasLabel(): Promise { + return (await this._label()) !== null; + } + + /** Gets the label of the form-field. */ + async getLabel(): Promise { + const labelEl = await this._label(); + return labelEl ? labelEl.text() : null; + } + + /** Whether the form-field has errors. */ + async hasErrors(): Promise { + return (await this.getTextErrors()).length > 0; + } + + /** Whether the label is currently floating. */ + async isLabelFloating(): Promise { + const labelEl = await this._label(); + return labelEl !== null ? await labelEl.hasClass('mdc-floating-label--float-above') : false; + } + + /** Whether the form-field is disabled. */ + async isDisabled(): Promise { + return (await this.host()).hasClass('mat-form-field-disabled'); + } + + /** Whether the form-field is currently autofilled. */ + async isAutofilled(): Promise { + return (await this.host()).hasClass('mat-form-field-autofilled'); + } + + /** Gets the theme color of the form-field. */ + async getThemeColor(): Promise<'primary'|'accent'|'warn'> { + const hostEl = await this.host(); + const [isAccent, isWarn] = + await Promise.all([hostEl.hasClass('mat-accent'), hostEl.hasClass('mat-warn')]); + if (isAccent) { + return 'accent'; + } else if (isWarn) { + return 'warn'; + } + return 'primary'; + } + + /** Gets error messages which are currently displayed in the form-field. */ + async getTextErrors(): Promise { + return Promise.all((await this._errors()).map(e => e.text())); + } + + /** Gets hint messages which are currently displayed in the form-field. */ + async getTextHints(): Promise { + return Promise.all((await this._hints()).map(e => e.text())); + } + + /** + * Gets a reference to the container element which contains all projected + * prefixes of the form-field. + */ + async getHarnessLoaderForPrefix(): Promise { + return this._prefixContainer(); + } + + /** + * Gets a reference to the container element which contains all projected + * suffixes of the form-field. + */ + async getHarnessLoaderForSuffix(): Promise { + return this._suffixContainer(); + } + + /** + * Whether the form control has been touched. Returns "null" + * if no form control is set up. + */ + async isControlTouched(): Promise { + if (!await this._hasFormControl()) { + return null; + } + return (await this.host()).hasClass('ng-touched'); + } + + /** + * Whether the form control is dirty. Returns "null" + * if no form control is set up. + */ + async isControlDirty(): Promise { + if (!await this._hasFormControl()) { + return null; + } + return (await this.host()).hasClass('ng-dirty'); + } + + /** + * Whether the form control is valid. Returns "null" + * if no form control is set up. + */ + async isControlValid(): Promise { + if (!await this._hasFormControl()) { + return null; + } + return (await this.host()).hasClass('ng-valid'); + } + + /** + * Whether the form control is pending validation. Returns "null" + * if no form control is set up. + */ + async isControlPending(): Promise { + if (!await this._hasFormControl()) { + return null; + } + return (await this.host()).hasClass('ng-pending'); + } + + /** Checks whether the form-field control has set up a form control. */ + private async _hasFormControl(): Promise { + const hostEl = await this.host(); + // If no form "NgControl" is bound to the form-field control, the form-field + // is not able to forward any control status classes. Therefore if either the + // "ng-touched" or "ng-untouched" class is set, we know that it has a form control + const [isTouched, isUntouched] = + await Promise.all([hostEl.hasClass('ng-touched'), hostEl.hasClass('ng-untouched')]); + return isTouched || isUntouched; + } +} diff --git a/src/material-experimental/mdc-form-field/testing/index.ts b/src/material-experimental/mdc-form-field/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-form-field/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-form-field/testing/public-api.ts b/src/material-experimental/mdc-form-field/testing/public-api.ts new file mode 100644 index 000000000000..3c64d4b8540c --- /dev/null +++ b/src/material-experimental/mdc-form-field/testing/public-api.ts @@ -0,0 +1,15 @@ +/** + * @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 + */ + +// Re-export everything from the "form-field/testing/control" entry-point. To avoid +// circular dependencies, harnesses for default form-field controls (i.e. input, select) +// need to import the base form-field control harness through a separate entry-point. +export * from '@angular/material/form-field/testing/control'; + +export {FormFieldHarnessFilters} from '@angular/material/form-field/testing'; +export * from './form-field-harness'; diff --git a/src/material-experimental/mdc-helpers/BUILD.bazel b/src/material-experimental/mdc-helpers/BUILD.bazel index 77758bde817c..e1d5aabe4f00 100644 --- a/src/material-experimental/mdc-helpers/BUILD.bazel +++ b/src/material-experimental/mdc-helpers/BUILD.bazel @@ -1,18 +1,10 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ng_module", "sass_library") +load("//tools:defaults.bzl", "sass_library") -ng_module( +filegroup( name = "mdc-helpers", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - assets = glob(["**/*.html"]), - module_name = "@angular/material-experimental/mdc-helpers", - deps = [ - "@npm//@angular/core", - ], + srcs = [":mdc_helpers_scss_lib"], ) # Add all MDC Sass files that we depend on here. After updating MDC dependencies, use the following @@ -24,112 +16,214 @@ ng_module( sass_library( name = "mdc_scss_deps_lib", srcs = [ + "@npm//:node_modules/@material/animation/_functions.import.scss", "@npm//:node_modules/@material/animation/_functions.scss", + "@npm//:node_modules/@material/animation/_variables.import.scss", "@npm//:node_modules/@material/animation/_variables.scss", + "@npm//:node_modules/@material/base/_mixins.import.scss", "@npm//:node_modules/@material/base/_mixins.scss", + "@npm//:node_modules/@material/button/_mixins.import.scss", "@npm//:node_modules/@material/button/_mixins.scss", + "@npm//:node_modules/@material/button/_variables.import.scss", "@npm//:node_modules/@material/button/_variables.scss", + "@npm//:node_modules/@material/card/_mixins.import.scss", "@npm//:node_modules/@material/card/_mixins.scss", + "@npm//:node_modules/@material/card/_variables.import.scss", "@npm//:node_modules/@material/card/_variables.scss", + "@npm//:node_modules/@material/checkbox/_functions.import.scss", "@npm//:node_modules/@material/checkbox/_functions.scss", + "@npm//:node_modules/@material/checkbox/_keyframes.import.scss", "@npm//:node_modules/@material/checkbox/_keyframes.scss", + "@npm//:node_modules/@material/checkbox/_mixins.import.scss", "@npm//:node_modules/@material/checkbox/_mixins.scss", + "@npm//:node_modules/@material/checkbox/_variables.import.scss", "@npm//:node_modules/@material/checkbox/_variables.scss", + "@npm//:node_modules/@material/chips/_mixins.import.scss", "@npm//:node_modules/@material/chips/_mixins.scss", + "@npm//:node_modules/@material/chips/_variables.import.scss", "@npm//:node_modules/@material/chips/_variables.scss", + "@npm//:node_modules/@material/data-table/_mixins.import.scss", "@npm//:node_modules/@material/data-table/_mixins.scss", + "@npm//:node_modules/@material/data-table/_variables.import.scss", "@npm//:node_modules/@material/data-table/_variables.scss", + "@npm//:node_modules/@material/density/_functions.import.scss", "@npm//:node_modules/@material/density/_functions.scss", + "@npm//:node_modules/@material/density/_variables.import.scss", "@npm//:node_modules/@material/density/_variables.scss", + "@npm//:node_modules/@material/dialog/_mixins.import.scss", "@npm//:node_modules/@material/dialog/_mixins.scss", + "@npm//:node_modules/@material/dialog/_variables.import.scss", "@npm//:node_modules/@material/dialog/_variables.scss", + "@npm//:node_modules/@material/drawer/_mixins.import.scss", "@npm//:node_modules/@material/drawer/_mixins.scss", + "@npm//:node_modules/@material/drawer/_variables.import.scss", "@npm//:node_modules/@material/drawer/_variables.scss", + "@npm//:node_modules/@material/elevation/_functions.import.scss", "@npm//:node_modules/@material/elevation/_functions.scss", + "@npm//:node_modules/@material/elevation/_mixins.import.scss", "@npm//:node_modules/@material/elevation/_mixins.scss", + "@npm//:node_modules/@material/elevation/_variables.import.scss", "@npm//:node_modules/@material/elevation/_variables.scss", + "@npm//:node_modules/@material/fab/_mixins.import.scss", "@npm//:node_modules/@material/fab/_mixins.scss", + "@npm//:node_modules/@material/fab/_variables.import.scss", "@npm//:node_modules/@material/fab/_variables.scss", + "@npm//:node_modules/@material/feature-targeting/_functions.import.scss", "@npm//:node_modules/@material/feature-targeting/_functions.scss", + "@npm//:node_modules/@material/feature-targeting/_mixins.import.scss", "@npm//:node_modules/@material/feature-targeting/_mixins.scss", + "@npm//:node_modules/@material/feature-targeting/_variables.import.scss", "@npm//:node_modules/@material/feature-targeting/_variables.scss", + "@npm//:node_modules/@material/floating-label/_mixins.import.scss", "@npm//:node_modules/@material/floating-label/_mixins.scss", + "@npm//:node_modules/@material/floating-label/_variables.import.scss", "@npm//:node_modules/@material/floating-label/_variables.scss", + "@npm//:node_modules/@material/form-field/_mixins.import.scss", "@npm//:node_modules/@material/form-field/_mixins.scss", + "@npm//:node_modules/@material/form-field/_variables.import.scss", "@npm//:node_modules/@material/form-field/_variables.scss", - "@npm//:node_modules/@material/grid-list/_mixins.scss", - "@npm//:node_modules/@material/grid-list/_variables.scss", + "@npm//:node_modules/@material/icon-button/_mixins.import.scss", "@npm//:node_modules/@material/icon-button/_mixins.scss", + "@npm//:node_modules/@material/icon-button/_variables.import.scss", "@npm//:node_modules/@material/icon-button/_variables.scss", + "@npm//:node_modules/@material/image-list/_mixins.import.scss", "@npm//:node_modules/@material/image-list/_mixins.scss", + "@npm//:node_modules/@material/image-list/_variables.import.scss", "@npm//:node_modules/@material/image-list/_variables.scss", + "@npm//:node_modules/@material/layout-grid/_mixins.import.scss", "@npm//:node_modules/@material/layout-grid/_mixins.scss", + "@npm//:node_modules/@material/layout-grid/_variables.import.scss", "@npm//:node_modules/@material/layout-grid/_variables.scss", + "@npm//:node_modules/@material/line-ripple/_functions.import.scss", "@npm//:node_modules/@material/line-ripple/_functions.scss", + "@npm//:node_modules/@material/line-ripple/_mixins.import.scss", "@npm//:node_modules/@material/line-ripple/_mixins.scss", + "@npm//:node_modules/@material/linear-progress/_keyframes.import.scss", "@npm//:node_modules/@material/linear-progress/_keyframes.scss", + "@npm//:node_modules/@material/linear-progress/_mixins.import.scss", "@npm//:node_modules/@material/linear-progress/_mixins.scss", + "@npm//:node_modules/@material/linear-progress/_variables.import.scss", "@npm//:node_modules/@material/linear-progress/_variables.scss", + "@npm//:node_modules/@material/list/_mixins.import.scss", "@npm//:node_modules/@material/list/_mixins.scss", + "@npm//:node_modules/@material/list/_variables.import.scss", "@npm//:node_modules/@material/list/_variables.scss", + "@npm//:node_modules/@material/menu-surface/_mixins.import.scss", "@npm//:node_modules/@material/menu-surface/_mixins.scss", + "@npm//:node_modules/@material/menu-surface/_variables.import.scss", "@npm//:node_modules/@material/menu-surface/_variables.scss", + "@npm//:node_modules/@material/menu/_mixins.import.scss", "@npm//:node_modules/@material/menu/_mixins.scss", + "@npm//:node_modules/@material/menu/_variables.import.scss", "@npm//:node_modules/@material/menu/_variables.scss", + "@npm//:node_modules/@material/notched-outline/_mixins.import.scss", "@npm//:node_modules/@material/notched-outline/_mixins.scss", + "@npm//:node_modules/@material/notched-outline/_variables.import.scss", "@npm//:node_modules/@material/notched-outline/_variables.scss", + "@npm//:node_modules/@material/radio/_functions.import.scss", "@npm//:node_modules/@material/radio/_functions.scss", + "@npm//:node_modules/@material/radio/_mixins.import.scss", "@npm//:node_modules/@material/radio/_mixins.scss", + "@npm//:node_modules/@material/radio/_variables.import.scss", "@npm//:node_modules/@material/radio/_variables.scss", + "@npm//:node_modules/@material/ripple/_functions.import.scss", "@npm//:node_modules/@material/ripple/_functions.scss", + "@npm//:node_modules/@material/ripple/_keyframes.import.scss", "@npm//:node_modules/@material/ripple/_keyframes.scss", + "@npm//:node_modules/@material/ripple/_mixins.import.scss", "@npm//:node_modules/@material/ripple/_mixins.scss", + "@npm//:node_modules/@material/ripple/_variables.import.scss", "@npm//:node_modules/@material/ripple/_variables.scss", + "@npm//:node_modules/@material/rtl/_mixins.import.scss", "@npm//:node_modules/@material/rtl/_mixins.scss", + "@npm//:node_modules/@material/rtl/_variables.import.scss", "@npm//:node_modules/@material/rtl/_variables.scss", + "@npm//:node_modules/@material/select/_functions.import.scss", "@npm//:node_modules/@material/select/_functions.scss", "@npm//:node_modules/@material/select/_keyframes.scss", + "@npm//:node_modules/@material/select/_mixins.import.scss", "@npm//:node_modules/@material/select/_mixins.scss", + "@npm//:node_modules/@material/select/_variables.import.scss", "@npm//:node_modules/@material/select/_variables.scss", + "@npm//:node_modules/@material/select/helper-text/_mixins.import.scss", "@npm//:node_modules/@material/select/helper-text/_mixins.scss", + "@npm//:node_modules/@material/select/icon/_mixins.import.scss", "@npm//:node_modules/@material/select/icon/_mixins.scss", + "@npm//:node_modules/@material/select/icon/_variables.import.scss", "@npm//:node_modules/@material/select/icon/_variables.scss", + "@npm//:node_modules/@material/shape/_functions.import.scss", "@npm//:node_modules/@material/shape/_functions.scss", + "@npm//:node_modules/@material/shape/_mixins.import.scss", "@npm//:node_modules/@material/shape/_mixins.scss", + "@npm//:node_modules/@material/shape/_variables.import.scss", "@npm//:node_modules/@material/shape/_variables.scss", + "@npm//:node_modules/@material/slider/_keyframes.import.scss", "@npm//:node_modules/@material/slider/_keyframes.scss", + "@npm//:node_modules/@material/slider/_mixins.import.scss", "@npm//:node_modules/@material/slider/_mixins.scss", + "@npm//:node_modules/@material/slider/_variables.import.scss", "@npm//:node_modules/@material/slider/_variables.scss", + "@npm//:node_modules/@material/snackbar/_mixins.import.scss", "@npm//:node_modules/@material/snackbar/_mixins.scss", + "@npm//:node_modules/@material/snackbar/_variables.import.scss", "@npm//:node_modules/@material/snackbar/_variables.scss", + "@npm//:node_modules/@material/switch/_functions.import.scss", "@npm//:node_modules/@material/switch/_functions.scss", + "@npm//:node_modules/@material/switch/_mixins.import.scss", "@npm//:node_modules/@material/switch/_mixins.scss", + "@npm//:node_modules/@material/switch/_variables.import.scss", "@npm//:node_modules/@material/switch/_variables.scss", + "@npm//:node_modules/@material/tab-bar/_mixins.import.scss", "@npm//:node_modules/@material/tab-bar/_mixins.scss", + "@npm//:node_modules/@material/tab-bar/_variables.import.scss", "@npm//:node_modules/@material/tab-bar/_variables.scss", + "@npm//:node_modules/@material/tab-indicator/_mixins.import.scss", "@npm//:node_modules/@material/tab-indicator/_mixins.scss", + "@npm//:node_modules/@material/tab-scroller/_mixins.import.scss", "@npm//:node_modules/@material/tab-scroller/_mixins.scss", + "@npm//:node_modules/@material/tab-scroller/_variables.import.scss", "@npm//:node_modules/@material/tab-scroller/_variables.scss", + "@npm//:node_modules/@material/tab/_mixins.import.scss", "@npm//:node_modules/@material/tab/_mixins.scss", + "@npm//:node_modules/@material/tab/_variables.import.scss", "@npm//:node_modules/@material/tab/_variables.scss", + "@npm//:node_modules/@material/textfield/_functions.import.scss", "@npm//:node_modules/@material/textfield/_functions.scss", + "@npm//:node_modules/@material/textfield/_mixins.import.scss", "@npm//:node_modules/@material/textfield/_mixins.scss", + "@npm//:node_modules/@material/textfield/_variables.import.scss", "@npm//:node_modules/@material/textfield/_variables.scss", + "@npm//:node_modules/@material/textfield/character-counter/_mixins.import.scss", "@npm//:node_modules/@material/textfield/character-counter/_mixins.scss", + "@npm//:node_modules/@material/textfield/helper-text/_mixins.import.scss", "@npm//:node_modules/@material/textfield/helper-text/_mixins.scss", + "@npm//:node_modules/@material/textfield/icon/_mixins.import.scss", "@npm//:node_modules/@material/textfield/icon/_mixins.scss", + "@npm//:node_modules/@material/textfield/icon/_variables.import.scss", "@npm//:node_modules/@material/textfield/icon/_variables.scss", + "@npm//:node_modules/@material/theme/_color-palette.import.scss", "@npm//:node_modules/@material/theme/_color-palette.scss", + "@npm//:node_modules/@material/theme/_constants.import.scss", "@npm//:node_modules/@material/theme/_constants.scss", + "@npm//:node_modules/@material/theme/_functions.import.scss", "@npm//:node_modules/@material/theme/_functions.scss", + "@npm//:node_modules/@material/theme/_mixins.import.scss", "@npm//:node_modules/@material/theme/_mixins.scss", + "@npm//:node_modules/@material/theme/_variables.import.scss", "@npm//:node_modules/@material/theme/_variables.scss", + "@npm//:node_modules/@material/top-app-bar/_mixins.import.scss", "@npm//:node_modules/@material/top-app-bar/_mixins.scss", + "@npm//:node_modules/@material/top-app-bar/_variables.import.scss", "@npm//:node_modules/@material/top-app-bar/_variables.scss", + "@npm//:node_modules/@material/touch-target/_mixins.import.scss", "@npm//:node_modules/@material/touch-target/_mixins.scss", + "@npm//:node_modules/@material/touch-target/_variables.import.scss", "@npm//:node_modules/@material/touch-target/_variables.scss", + "@npm//:node_modules/@material/typography/_functions.import.scss", "@npm//:node_modules/@material/typography/_functions.scss", + "@npm//:node_modules/@material/typography/_mixins.import.scss", "@npm//:node_modules/@material/typography/_mixins.scss", + "@npm//:node_modules/@material/typography/_variables.import.scss", "@npm//:node_modules/@material/typography/_variables.scss", ], ) diff --git a/src/material-experimental/mdc-helpers/_mdc-helpers.scss b/src/material-experimental/mdc-helpers/_mdc-helpers.scss index d5991fce2e0e..d84a77dbfdcf 100644 --- a/src/material-experimental/mdc-helpers/_mdc-helpers.scss +++ b/src/material-experimental/mdc-helpers/_mdc-helpers.scss @@ -2,10 +2,10 @@ // "theming", "typography", "core". Currently splitting it is difficult because of our brittle // gulp-based release build process. We can update this when we switch to bazel. -@import '@material/feature-targeting/functions'; -@import '@material/theme/functions'; -@import '@material/theme/variables'; -@import '@material/typography/variables'; +@import '@material/feature-targeting/functions.import'; +@import '@material/theme/functions.import'; +@import '@material/theme/variables.import'; +@import '@material/typography/variables.import'; @import '../../material/core/theming/theming'; @import '../../material/core/typography/typography'; diff --git a/src/material-experimental/mdc-helpers/public-api.ts b/src/material-experimental/mdc-helpers/public-api.ts deleted file mode 100644 index c35cd44d546b..000000000000 --- a/src/material-experimental/mdc-helpers/public-api.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @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'; - -// TODO(mmalerba): This is needed so the `mdc-helpers` directory will be counted as a secondary -// entry point by our gulp build system. Being a secondary entry point ensures that the Sass -// partial is copied to the root of the release. When we switch to bazel for building our releases -// we can delete this. -@NgModule({}) -export class MatMdcHelpersModule { -} diff --git a/src/material-experimental/mdc-input/BUILD.bazel b/src/material-experimental/mdc-input/BUILD.bazel new file mode 100644 index 000000000000..db06b3245844 --- /dev/null +++ b/src/material-experimental/mdc-input/BUILD.bazel @@ -0,0 +1,88 @@ +package(default_visibility = ["//visibility:public"]) + +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_library", +) + +ng_module( + name = "mdc-input", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + assets = glob(["**/*.html"]), + module_name = "@angular/material-experimental/mdc-input", + deps = [ + "//src/material-experimental/mdc-form-field", + "//src/material/core", + "//src/material/input", + "@npm//@angular/forms", + "@npm//@material/textfield", + ], +) + +sass_library( + name = "mdc_input_scss_lib", + srcs = glob(["**/_*.scss"]), + deps = [ + "//src/cdk/a11y:a11y_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + ], +) + +########### +# Testing +########### + +ng_test_library( + name = "input_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["**/*.e2e.spec.ts"], + ), + deps = [ + ":mdc-input", + "//src/cdk/platform", + "//src/cdk/testing/private", + "//src/material-experimental/mdc-form-field", + "//src/material/core", + "@npm//@angular/forms", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = [ + "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", + "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", + "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", + "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", + ], + deps = [ + ":input_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-input/README.md b/src/material-experimental/mdc-input/README.md new file mode 100644 index 000000000000..73eeea50298e --- /dev/null +++ b/src/material-experimental/mdc-input/README.md @@ -0,0 +1 @@ +This is a placeholder for the MDC-based implementation of input. diff --git a/src/material-experimental/mdc-input/_mdc-input.scss b/src/material-experimental/mdc-input/_mdc-input.scss new file mode 100644 index 000000000000..fc5826be566b --- /dev/null +++ b/src/material-experimental/mdc-input/_mdc-input.scss @@ -0,0 +1,9 @@ +@import '../mdc-helpers/mdc-helpers'; + +@mixin mat-input-theme-mdc($theme) { + @include mat-using-mdc-theme($theme) {} +} + +@mixin mat-input-typography-mdc($config) { + @include mat-using-mdc-typography($config) {} +} diff --git a/src/material-experimental/mdc-input/index.ts b/src/material-experimental/mdc-input/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-input/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-input/input.e2e.spec.ts b/src/material-experimental/mdc-input/input.e2e.spec.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/material-experimental/mdc-input/input.spec.ts b/src/material-experimental/mdc-input/input.spec.ts new file mode 100644 index 000000000000..fa355b0d65b2 --- /dev/null +++ b/src/material-experimental/mdc-input/input.spec.ts @@ -0,0 +1,1603 @@ +import {Platform, PlatformModule} from '@angular/cdk/platform'; +import {dispatchFakeEvent, wrappedErrorMessage} from '@angular/cdk/testing/private'; +import { + ChangeDetectionStrategy, + Component, + Directive, + ErrorHandler, + Provider, + Type, + ViewChild, +} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; +import { + FormControl, + FormGroup, + FormGroupDirective, + FormsModule, + NgForm, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { + getMatFormFieldDuplicatedHintError, + getMatFormFieldMissingControlError, + MAT_FORM_FIELD_DEFAULT_OPTIONS, + MatFormField, + MatFormFieldAppearance, + MatFormFieldModule, +} from '@angular/material-experimental/mdc-form-field'; +import { + ErrorStateMatcher, + MAT_LABEL_GLOBAL_OPTIONS, + ShowOnDirtyErrorStateMatcher, +} from '@angular/material/core'; +import {By} from '@angular/platform-browser'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {MAT_INPUT_VALUE_ACCESSOR, MatInput, MatInputModule} from './index'; + +describe('MatMdcInput without forms', () => { + it('should default to floating labels', fakeAsync(() => { + let fixture = createComponent(MatInputWithLabel); + fixture.detectChanges(); + + let formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField.floatLabel).toBe('auto', + 'Expected MatInput to set floatingLabel to auto by default.'); + })); + + it('should default to global floating label type', fakeAsync(() => { + let fixture = createComponent(MatInputWithLabel, [{ + provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: {float: 'always'} + }]); + fixture.detectChanges(); + + let formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField.floatLabel).toBe('always', + 'Expected MatInput to set floatingLabel to always from global option.'); + })); + + it('should not be treated as empty if type is date', fakeAsync(() => { + const platform = new Platform(); + + if (!(platform.TRIDENT || (platform.SAFARI && !platform.IOS))) { + const fixture = createComponent(MatInputDateTestController); + fixture.detectChanges(); + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(false); + } + })); + + // Safari Desktop and IE don't support type="date" and fallback to type="text". + it('should be treated as empty if type is date in Safari Desktop or IE', fakeAsync(() => { + const platform = new Platform(); + + if (platform.TRIDENT || (platform.SAFARI && !platform.IOS)) { + let fixture = createComponent(MatInputDateTestController); + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(true); + } + })); + + it('should treat text input type as empty at init', fakeAsync(() => { + let fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(true); + })); + + it('should treat password input type as empty at init', fakeAsync(() => { + let fixture = createComponent(MatInputPasswordTestController); + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(true); + })); + + it('should treat number input type as empty at init', fakeAsync(() => { + let fixture = createComponent(MatInputNumberTestController); + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(true); + })); + + it('should not be empty after input entered', fakeAsync(() => { + let fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input'))!; + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField).toBeTruthy(); + expect(formField!._control.empty).toBe(true); + + inputEl.nativeElement.value = 'hello'; + // Simulate input event. + inputEl.triggerEventHandler('input', {target: inputEl.nativeElement}); + fixture.detectChanges(); + expect(formField!._control.empty).toBe(false); + })); + + it('should update the placeholder when input entered', fakeAsync(() => { + let fixture = createComponent(MatInputWithStaticLabel); + fixture.detectChanges(); + + const inputEl = fixture.debugElement.query(By.css('input'))!; + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + + expect(formField).toBeTruthy(); + expect(formField._control.empty).toBe(true); + expect(formField._shouldLabelFloat()).toBe(false); + + // Update the value of the input. + inputEl.nativeElement.value = 'Text'; + + // Fake behavior of the `(input)` event which should trigger a change detection. + fixture.detectChanges(); + + expect(formField._control.empty).toBe(false); + expect(formField._shouldLabelFloat()).toBe(true); + })); + + it('should not be empty when the value set before view init', fakeAsync(() => { + let fixture = createComponent(MatInputWithValueBinding); + fixture.detectChanges(); + const formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField._control.empty).toBe(false); + + fixture.componentInstance.value = ''; + fixture.detectChanges(); + + expect(formField._control.empty).toBe(true); + })); + + it('should add id', fakeAsync(() => { + let fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + const inputElement: HTMLInputElement = + fixture.debugElement.query(By.css('input'))!.nativeElement; + const labelElement: HTMLInputElement = + fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(inputElement.id).toBeTruthy(); + expect(inputElement.id).toEqual(labelElement.getAttribute('for')!); + })); + + it('should add aria-owns to the label for the associated control', fakeAsync(() => { + let fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + const inputElement: HTMLInputElement = + fixture.debugElement.query(By.css('input'))!.nativeElement; + const labelElement: HTMLInputElement = + fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(labelElement.getAttribute('aria-owns')).toBe(inputElement.id); + })); + + it('should add aria-required reflecting the required state', fakeAsync(() => { + const fixture = createComponent(MatInputWithRequired); + fixture.detectChanges(); + + const inputElement: HTMLInputElement = + fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(inputElement.getAttribute('aria-required')) + .toBe('false', 'Expected aria-required to reflect required state of false'); + + fixture.componentInstance.required = true; + fixture.detectChanges(); + + expect(inputElement.getAttribute('aria-required')) + .toBe('true', 'Expected aria-required to reflect required state of true'); + })); + + it('should not overwrite existing id', fakeAsync(() => { + let fixture = createComponent(MatInputWithId); + fixture.detectChanges(); + + const inputElement: HTMLInputElement = + fixture.debugElement.query(By.css('input'))!.nativeElement; + const labelElement: HTMLInputElement = + fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(inputElement.id).toBe('test-id'); + expect(labelElement.getAttribute('for')).toBe('test-id'); + })); + + it('validates there\'s only one hint label per side', () => { + let fixture = createComponent(MatInputInvalidHintTestController); + expect(() => fixture.detectChanges()).toThrowError( + wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start'))); + }); + + it('validates there\'s only one hint label per side (attribute)', () => { + let fixture = createComponent(MatInputInvalidHint2TestController); + + expect(() => fixture.detectChanges()).toThrowError( + wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start'))); + }); + + it('validates that matInput child is present', fakeAsync(() => { + let fixture = createComponent(MatInputMissingMatInputTestController); + + expect(() => fixture.detectChanges()).toThrowError( + wrappedErrorMessage(getMatFormFieldMissingControlError())); + })); + + it('validates that matInput child is present after initialization', fakeAsync(() => { + let fixture = createComponent(MatInputWithNgIf); + + expect(() => fixture.detectChanges()).not.toThrowError( + wrappedErrorMessage(getMatFormFieldMissingControlError())); + + fixture.componentInstance.renderInput = false; + + expect(() => fixture.detectChanges()).toThrowError( + wrappedErrorMessage(getMatFormFieldMissingControlError())); + })); + + it('validates the type', fakeAsync(() => { + let fixture = createComponent(MatInputInvalidTypeTestController); + + // Technically this throws during the OnChanges detection phase, + // so the error is really a ChangeDetectionError and it becomes + // hard to build a full exception to compare with. + // We just check for any exception in this case. + expect(() => fixture.detectChanges()).toThrow( + /* new MatInputUnsupportedTypeError('file') */); + })); + + it('supports hint labels attribute', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabelTestController); + fixture.detectChanges(); + + // If the hint label is empty, expect no label. + expect(fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))).toBeNull(); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))).not.toBeNull(); + })); + + it('sets an id on hint labels', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabelTestController); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + + let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement; + + expect(hint.getAttribute('id')).toBeTruthy(); + })); + + it('supports hint labels elements', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabel2TestController); + fixture.detectChanges(); + + // In this case, we should have an empty . + let el = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement; + expect(el.textContent).toBeFalsy(); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + el = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement; + expect(el.textContent).toBe('label'); + })); + + it('sets an id on the hint element', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabel2TestController); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + + let hint = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement; + + expect(hint.getAttribute('id')).toBeTruthy(); + })); + + it('supports label required star', fakeAsync(() => { + let fixture = createComponent(MatInputLabelRequiredTestComponent); + fixture.detectChanges(); + + let el = fixture.debugElement.query(By.css('label'))!; + expect(el).not.toBeNull(); + expect(el.nativeElement.textContent).toBe('hello *'); + })); + + it('should hide the required star if input is disabled', () => { + const fixture = createComponent(MatInputLabelRequiredTestComponent); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + const el = fixture.debugElement.query(By.css('label'))!; + + expect(el).not.toBeNull(); + expect(el.nativeElement.textContent).toBe('hello'); + }); + + it('should hide the required star from screen readers', fakeAsync(() => { + let fixture = createComponent(MatInputLabelRequiredTestComponent); + fixture.detectChanges(); + + let el = fixture.debugElement + .query(By.css('.mat-mdc-form-field-required-marker'))!.nativeElement; + + expect(el.getAttribute('aria-hidden')).toBe('true'); + })); + + it('hide label required star when set to hide the required marker', fakeAsync(() => { + let fixture = createComponent(MatInputLabelRequiredTestComponent); + fixture.detectChanges(); + + let el = fixture.debugElement.query(By.css('label'))!; + expect(el).not.toBeNull(); + expect(el.nativeElement.textContent).toBe('hello *'); + + fixture.componentInstance.hideRequiredMarker = true; + fixture.detectChanges(); + + expect(el.nativeElement.textContent).toBe('hello'); + })); + + it('supports the disabled attribute as binding', fakeAsync(() => { + const fixture = createComponent(MatInputWithDisabled); + fixture.detectChanges(); + + const wrapperEl = + fixture.debugElement.query(By.css('.mat-mdc-text-field-wrapper'))!.nativeElement; + const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(wrapperEl.classList.contains('mdc-text-field--disabled')) + .toBe(false, `Expected form field not to start out disabled.`); + expect(inputEl.disabled).toBe(false); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(wrapperEl.classList.contains('mdc-text-field--disabled')) + .toBe(true, `Expected form field to look disabled after property is set.`); + expect(inputEl.disabled).toBe(true); + })); + + it('supports the disabled attribute as binding for select', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const wrapperEl = + fixture.debugElement.query(By.css('.mat-mdc-text-field-wrapper'))!.nativeElement; + const selectEl = fixture.debugElement.query(By.css('select'))!.nativeElement; + + expect(wrapperEl.classList.contains('mdc-text-field--disabled')) + .toBe(false, `Expected form field not to start out disabled.`); + expect(selectEl.disabled).toBe(false); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(wrapperEl.classList.contains('mdc-text-field--disabled')) + .toBe(true, `Expected form field to look disabled after property is set.`); + expect(selectEl.disabled).toBe(true); + })); + + it('should add a class to the form field if it has a native select', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + expect(formField.classList).toContain('mat-mdc-form-field-type-mat-native-select'); + })); + + it('supports the required attribute as binding', fakeAsync(() => { + let fixture = createComponent(MatInputWithRequired); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(inputEl.required).toBe(false); + + fixture.componentInstance.required = true; + fixture.detectChanges(); + + expect(inputEl.required).toBe(true); + })); + + it('supports the required attribute as binding for select', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const selectEl = fixture.debugElement.query(By.css('select'))!.nativeElement; + + expect(selectEl.required).toBe(false); + + fixture.componentInstance.required = true; + fixture.detectChanges(); + + expect(selectEl.required).toBe(true); + })); + + it('supports the type attribute as binding', fakeAsync(() => { + let fixture = createComponent(MatInputWithType); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(inputEl.type).toBe('text'); + + fixture.componentInstance.type = 'password'; + fixture.detectChanges(); + + expect(inputEl.type).toBe('password'); + })); + + it('supports textarea', fakeAsync(() => { + let fixture = createComponent(MatInputTextareaWithBindings); + fixture.detectChanges(); + + const textarea: HTMLTextAreaElement = fixture.nativeElement.querySelector('textarea'); + expect(textarea).not.toBeNull(); + })); + + it('supports select', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const nativeSelect: HTMLTextAreaElement = fixture.nativeElement.querySelector('select'); + expect(nativeSelect).not.toBeNull(); + })); + + it('sets the aria-describedby when a hintLabel is set', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabelTestController); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + + let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement; + let input = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id')); + })); + + it('sets the aria-describedby to the id of the mat-hint', fakeAsync(() => { + let fixture = createComponent(MatInputHintLabel2TestController); + + fixture.componentInstance.label = 'label'; + fixture.detectChanges(); + + let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement; + let input = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id')); + })); + + it('sets the aria-describedby with multiple mat-hint instances', fakeAsync(() => { + let fixture = createComponent(MatInputMultipleHintTestController); + + fixture.componentInstance.startId = 'start'; + fixture.componentInstance.endId = 'end'; + fixture.detectChanges(); + + let input = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(input.getAttribute('aria-describedby')).toBe('start end'); + })); + + it('sets the aria-describedby when a hintLabel is set, in addition to a mat-hint', + fakeAsync(() => { + let fixture = createComponent(MatInputMultipleHintMixedTestController); + + fixture.detectChanges(); + + let hintLabel = fixture.debugElement.query( + By.css('.mat-mdc-form-field-hint:not(.mat-form-field-hint-end)'))!.nativeElement; + let endLabel = fixture.debugElement + .query(By.css('.mat-mdc-form-field-hint.mat-form-field-hint-end'))!.nativeElement; + let input = fixture.debugElement.query(By.css('input'))!.nativeElement; + let ariaValue = input.getAttribute('aria-describedby'); + + expect(ariaValue).toBe(`${hintLabel.getAttribute('id')} ${endLabel.getAttribute('id')}`); + })); + + it('should float when floatLabel is set to default and text is entered', fakeAsync(() => { + let fixture = createComponent(MatInputWithDynamicLabel); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + let labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + + fixture.componentInstance.shouldFloat = 'auto'; + fixture.detectChanges(); + + expect(labelEl.classList).not.toContain('mdc-floating-label--float-above'); + + // Update the value of the input. + inputEl.value = 'Text'; + + // Fake behavior of the `(input)` event which should trigger a change detection. + fixture.detectChanges(); + + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should always float the label when floatLabel is set to always', fakeAsync(() => { + let fixture = createComponent(MatInputWithDynamicLabel); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + let labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + + fixture.detectChanges(); + + // Update the value of the input. + inputEl.value = 'Text'; + + // Fake behavior of the `(input)` event which should trigger a change detection. + fixture.detectChanges(); + + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should float labels when select has value', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should not float the label if the selectedIndex is negative', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + const selectEl: HTMLSelectElement = fixture.nativeElement.querySelector('select'); + + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + + selectEl.selectedIndex = -1; + fixture.detectChanges(); + + expect(labelEl.classList).not.toContain('mdc-floating-label--float-above'); + })); + + it('should not float labels when select has no value, no option label, ' + + 'no option innerHtml', fakeAsync(() => { + const fixture = createComponent(MatInputSelectWithNoLabelNoValue); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + expect(labelEl.classList).not.toContain('mdc-floating-label--float-above'); + })); + + it('should floating labels when select has no value but has option label', + fakeAsync(() => { + const fixture = createComponent(MatInputSelectWithLabel); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should floating labels when select has no value but has option innerHTML', + fakeAsync(() => { + const fixture = createComponent(MatInputSelectWithInnerHtml); + fixture.detectChanges(); + + const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement; + expect(labelEl.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should not throw if a native select does not have options', fakeAsync(() => { + const fixture = createComponent(MatInputSelectWithoutOptions); + expect(() => fixture.detectChanges()).not.toThrow(); + })); + + it('should be able to toggle the floating label programmatically', fakeAsync(() => { + const fixture = createComponent(MatInputWithId); + + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField))!; + const containerInstance = formField.componentInstance as MatFormField; + const label = formField.nativeElement.querySelector('label'); + + expect(containerInstance.floatLabel).toBe('auto'); + expect(label.classList).not.toContain('mdc-floating-label--float-above'); + + fixture.componentInstance.floatLabel = 'always'; + fixture.detectChanges(); + + expect(containerInstance.floatLabel).toBe('always'); + expect(label.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should not have prefix and suffix elements when none are specified', fakeAsync(() => { + let fixture = createComponent(MatInputWithId); + fixture.detectChanges(); + + let prefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-prefix')); + let suffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-suffix')); + + expect(prefixEl).toBeNull(); + expect(suffixEl).toBeNull(); + })); + + it('should add prefix and suffix elements when specified', fakeAsync(() => { + const fixture = createComponent(MatInputWithPrefixAndSuffix); + fixture.detectChanges(); + + const prefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-prefix'))!; + const suffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-suffix'))!; + + expect(prefixEl).not.toBeNull(); + expect(suffixEl).not.toBeNull(); + expect(prefixEl.nativeElement.innerText.trim()).toEqual('Prefix'); + expect(suffixEl.nativeElement.innerText.trim()).toEqual('Suffix'); + })); + + it('should update empty class when value changes programmatically and OnPush', fakeAsync(() => { + let fixture = createComponent(MatInputOnPush); + fixture.detectChanges(); + + let component = fixture.componentInstance; + let label = fixture.debugElement.query(By.css('label'))!.nativeElement; + + expect(label.classList).not.toContain('mdc-floating-label--float-above'); + + component.formControl.setValue('something'); + fixture.detectChanges(); + + expect(label.classList).toContain('mdc-floating-label--float-above'); + })); + + it('should set the focused class when the input is focused', fakeAsync(() => { + let fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + let input = fixture.debugElement.query(By.directive(MatInput))! + .injector.get(MatInput); + let container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + // Call the focus handler directly to avoid flakyness where + // browsers don't focus elements if the window is minimized. + input._focusChanged(true); + fixture.detectChanges(); + + expect(container.classList).toContain('mat-focused'); + })); + + it('should remove the focused class if the input becomes disabled while focused', + fakeAsync(() => { + const fixture = createComponent(MatInputTextTestController); + fixture.detectChanges(); + + const input = fixture.debugElement.query(By.directive(MatInput))! + .injector.get(MatInput); + const container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + // Call the focus handler directly to avoid flakyness where + // browsers don't focus elements if the window is minimized. + input._focusChanged(true); + fixture.detectChanges(); + + expect(container.classList).toContain('mat-focused'); + + input.disabled = true; + fixture.detectChanges(); + + expect(container.classList).not.toContain('mat-focused'); + })); + + it('should not highlight when focusing a readonly input', fakeAsync(() => { + let fixture = createComponent(MatInputWithReadonlyInput); + fixture.detectChanges(); + + let input = + fixture.debugElement.query(By.directive(MatInput))!.injector.get(MatInput); + let container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + // Call the focus handler directly to avoid flakiness where + // browsers don't focus elements if the window is minimized. + input._focusChanged(true); + fixture.detectChanges(); + + expect(input.focused).toBe(false); + expect(container.classList).not.toContain('mat-focused'); + })); + + it('should reset the highlight when a readonly input is blurred', fakeAsync(() => { + const fixture = createComponent(MatInputWithReadonlyInput); + fixture.detectChanges(); + + const inputDebugElement = fixture.debugElement.query(By.directive(MatInput))!; + const input = inputDebugElement.injector.get(MatInput); + const container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + fixture.componentInstance.isReadonly = false; + fixture.detectChanges(); + + // Call the focus handler directly to avoid flakyness where + // browsers don't focus elements if the window is minimized. + input._focusChanged(true); + fixture.detectChanges(); + + expect(input.focused).toBe(true); + expect(container.classList).toContain('mat-focused'); + + fixture.componentInstance.isReadonly = true; + fixture.detectChanges(); + + input._focusChanged(false); + fixture.detectChanges(); + + expect(input.focused).toBe(false); + expect(container.classList).not.toContain('mat-focused'); + })); + + it('should not add the `placeholder` attribute if there is no placeholder', () => { + const fixture = createComponent(MatInputWithoutPlaceholder); + fixture.detectChanges(); + const input = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(input.hasAttribute('placeholder')).toBe(false); + }); + + it('should not add the native select class if the control is not a native select', () => { + const fixture = createComponent(MatInputWithId); + fixture.detectChanges(); + const formField = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + expect(formField.classList).not.toContain('mat-mdc-form-field-type-mat-native-select'); + }); + + it('should use the native input value when determining whether ' + + 'the element is empty with a custom accessor', fakeAsync(() => { + let fixture = createComponent(MatInputWithCustomAccessor, [], [], [CustomMatInputAccessor]); + fixture.detectChanges(); + let formField = fixture.debugElement + .query(By.directive(MatFormField))!.componentInstance as MatFormField; + + expect(formField._control.empty).toBe(true); + + fixture.nativeElement.querySelector('input').value = 'abc'; + fixture.detectChanges(); + + expect(formField._control.empty).toBe(false); + })); + +}); + +describe('MatMdcInput with forms', () => { + describe('error messages', () => { + let fixture: ComponentFixture; + let testComponent: MatInputWithFormErrorMessages; + let containerEl: HTMLElement; + let inputEl: HTMLElement; + + beforeEach(fakeAsync(() => { + fixture = createComponent(MatInputWithFormErrorMessages); + fixture.detectChanges(); + testComponent = fixture.componentInstance; + containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + })); + + it('should not show any errors if the user has not interacted', fakeAsync(() => { + expect(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); + expect(inputEl.getAttribute('aria-invalid')) + .toBe('false', 'Expected aria-invalid to be set to "false".'); + })); + + it('should display an error message when the input is touched and invalid', fakeAsync(() => { + expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); + + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + flush(); + + expect(containerEl.classList) + .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error message to have been rendered.'); + expect(inputEl.getAttribute('aria-invalid')) + .toBe('true', 'Expected aria-invalid to be set to "true".'); + })); + + it('should not reset text-field validity if focus changes for an invalid input', + fakeAsync(() => { + // Mark the control as touched, so that the form-field displays as invalid. + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + flush(); + + const wrapperEl = containerEl.querySelector('.mdc-text-field')!; + expect(wrapperEl.classList).toContain('mdc-text-field--invalid'); + + dispatchFakeEvent(inputEl, 'focus'); + dispatchFakeEvent(inputEl, 'blur'); + expect(wrapperEl.classList).toContain('mdc-text-field--invalid'); + })); + + it('should display an error message when the parent form is submitted', fakeAsync(() => { + expect(testComponent.form.submitted).toBe(false, 'Expected form not to have been submitted'); + expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); + + dispatchFakeEvent(fixture.debugElement.query(By.css('form'))!.nativeElement, 'submit'); + fixture.detectChanges(); + flush(); + + expect(testComponent.form.submitted).toBe(true, 'Expected form to have been submitted'); + expect(containerEl.classList) + .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error message to have been rendered.'); + expect(inputEl.getAttribute('aria-invalid')) + .toBe('true', 'Expected aria-invalid to be set to "true".'); + })); + + it('should display an error message when the parent form group is submitted', fakeAsync(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + + let groupFixture = createComponent(MatInputWithFormGroupErrorMessages); + let component: MatInputWithFormGroupErrorMessages; + + groupFixture.detectChanges(); + component = groupFixture.componentInstance; + containerEl = groupFixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + inputEl = groupFixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(component.formGroup.invalid).toBe(true, 'Expected form control to be invalid'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); + expect(inputEl.getAttribute('aria-invalid')) + .toBe('false', 'Expected aria-invalid to be set to "false".'); + expect(component.formGroupDirective.submitted) + .toBe(false, 'Expected form not to have been submitted'); + + dispatchFakeEvent(groupFixture.debugElement.query(By.css('form'))!.nativeElement, 'submit'); + groupFixture.detectChanges(); + flush(); + + expect(component.formGroupDirective.submitted) + .toBe(true, 'Expected form to have been submitted'); + expect(containerEl.classList) + .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error message to have been rendered.'); + expect(inputEl.getAttribute('aria-invalid')) + .toBe('true', 'Expected aria-invalid to be set to "true".'); + })); + + it('should hide the errors and show the hints once the input becomes valid', fakeAsync(() => { + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + flush(); + + expect(containerEl.classList) + .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error message to have been rendered.'); + expect(containerEl.querySelectorAll('mat-hint').length) + .toBe(0, 'Expected no hints to be shown.'); + + testComponent.formControl.setValue('something'); + fixture.detectChanges(); + flush(); + + expect(containerEl.classList).not.toContain('mat-form-field-invalid', + 'Expected container not to have the invalid class when valid.'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(0, 'Expected no error messages when the input is valid.'); + expect(containerEl.querySelectorAll('mat-hint').length) + .toBe(1, 'Expected one hint to be shown once the input is valid.'); + })); + + it('should not hide the hint if there are no error messages', fakeAsync(() => { + testComponent.renderError = false; + fixture.detectChanges(); + + expect(containerEl.querySelectorAll('mat-hint').length) + .toBe(1, 'Expected one hint to be shown on load.'); + + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + flush(); + + expect(containerEl.querySelectorAll('mat-hint').length) + .toBe(1, 'Expected one hint to still be shown.'); + })); + + it('should set the proper role on the error messages', fakeAsync(() => { + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + + expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert'); + })); + + it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => { + let hintId = fixture.debugElement + .query(By.css('.mat-mdc-form-field-hint'))!.nativeElement.getAttribute('id'); + let describedBy = inputEl.getAttribute('aria-describedby'); + + expect(hintId).toBeTruthy('hint should be shown'); + expect(describedBy).toBe(hintId); + + fixture.componentInstance.formControl.markAsTouched(); + fixture.detectChanges(); + + let errorIds = fixture.debugElement.queryAll(By.css('.mat-mdc-form-field-error')) + .map(el => el.nativeElement.getAttribute('id')).join(' '); + describedBy = inputEl.getAttribute('aria-describedby'); + + expect(errorIds).toBeTruthy('errors should be shown'); + expect(describedBy).toBe(errorIds); + })); + }); + + describe('custom error behavior', () => { + + it('should display an error message when a custom error matcher returns true', fakeAsync(() => { + let fixture = createComponent(MatInputWithCustomErrorStateMatcher); + fixture.detectChanges(); + + let component = fixture.componentInstance; + let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + + const control = component.formGroup.get('name')!; + + expect(control.invalid).toBe(true, 'Expected form control to be invalid'); + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(0, 'Expected no error messages'); + + control.markAsTouched(); + fixture.detectChanges(); + + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(0, 'Expected no error messages after being touched.'); + + component.errorState = true; + fixture.detectChanges(); + + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error messages to have been rendered.'); + })); + + it('should display an error message when global error matcher returns true', fakeAsync(() => { + let fixture = createComponent(MatInputWithFormErrorMessages, [{ + provide: ErrorStateMatcher, useValue: {isErrorState: () => true}} + ]); + + fixture.detectChanges(); + + let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + let testComponent = fixture.componentInstance; + + // Expect the control to still be untouched but the error to show due to the global setting + expect(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(1, 'Expected an error message'); + })); + + it('should display an error message when using ShowOnDirtyErrorStateMatcher', fakeAsync(() => { + let fixture = createComponent(MatInputWithFormErrorMessages, [{ + provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher + }]); + fixture.detectChanges(); + + let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + let testComponent = fixture.componentInstance; + + expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); + expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); + + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(0, 'Expected no error messages when touched'); + + testComponent.formControl.markAsDirty(); + fixture.detectChanges(); + + expect(containerEl.querySelectorAll('mat-error').length) + .toBe(1, 'Expected one error message when dirty'); + })); + }); + + it('should update the value when using FormControl.setValue', fakeAsync(() => { + let fixture = createComponent(MatInputWithFormControl); + fixture.detectChanges(); + + let input = fixture.debugElement.query(By.directive(MatInput))! + .injector.get(MatInput); + + expect(input.value).toBeFalsy(); + + fixture.componentInstance.formControl.setValue('something'); + + expect(input.value).toBe('something'); + })); + + it('should display disabled styles when using FormControl.disable()', fakeAsync(() => { + const fixture = createComponent(MatInputWithFormControl); + fixture.detectChanges(); + + const formFieldEl = + fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement; + const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + + expect(formFieldEl.classList) + .not.toContain('mat-form-field-disabled', `Expected form field not to start out disabled.`); + expect(inputEl.disabled).toBe(false); + + fixture.componentInstance.formControl.disable(); + fixture.detectChanges(); + + expect(formFieldEl.classList).toContain('mat-form-field-disabled', + `Expected form field to look disabled after disable() is called.`); + expect(inputEl.disabled).toBe(true); + })); + + it('should not treat the number 0 as empty', fakeAsync(() => { + let fixture = createComponent(MatInputZeroTestController); + fixture.detectChanges(); + flush(); + + fixture.detectChanges(); + + let formField = fixture.debugElement + .query(By.directive(MatFormField))!.componentInstance as MatFormField; + expect(formField).not.toBeNull(); + expect(formField._control.empty).toBe(false); + })); + + it('should update when the form field value is patched without emitting', fakeAsync(() => { + const fixture = createComponent(MatInputWithFormControl); + fixture.detectChanges(); + + let formField = fixture.debugElement + .query(By.directive(MatFormField))!.componentInstance as MatFormField; + + expect(formField._control.empty).toBe(true); + + fixture.componentInstance.formControl.patchValue('value', {emitEvent: false}); + fixture.detectChanges(); + + expect(formField._control.empty).toBe(false); + })); + +}); + +describe('MatFormField default options', () => { + it('should be fill appearance if no default options provided', () => { + const fixture = createComponent(MatInputWithAppearance); + fixture.detectChanges(); + expect(fixture.componentInstance.formField.appearance).toBe('fill'); + }); + + it('should be fill appearance if empty default options provided', () => { + const fixture = createComponent(MatInputWithAppearance, [{ + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {}} + ]); + + fixture.detectChanges(); + expect(fixture.componentInstance.formField.appearance).toBe('fill'); + }); + + it('should be able to change the default appearance', () => { + const fixture = createComponent(MatInputWithAppearance, [{ + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}} + ]); + fixture.detectChanges(); + expect(fixture.componentInstance.formField.appearance).toBe('outline'); + }); + + it('should default hideRequiredMarker to false', () => { + const fixture = createComponent(MatInputWithAppearance, [{ + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {}} + ]); + + fixture.detectChanges(); + expect(fixture.componentInstance.formField.hideRequiredMarker).toBe(false); + }); + + it('should be able to change the default value of hideRequiredMarker', () => { + const fixture = createComponent(MatInputWithAppearance, [{ + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {hideRequiredMarker: true}} + ]); + + fixture.detectChanges(); + expect(fixture.componentInstance.formField.hideRequiredMarker).toBe(true); + }); + +}); + +function createComponent(component: Type, + providers: Provider[] = [], + imports: any[] = [], + declarations: any[] = []): ComponentFixture { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + MatFormFieldModule, + MatInputModule, + BrowserAnimationsModule, + PlatformModule, + ReactiveFormsModule, + ...imports + ], + declarations: [component, ...declarations], + providers: [ + // Custom error handler that re-throws the error. Errors happening within + // change detection phase will be reported through the handler and thrown + // in Ivy. Since we do not want to pollute the "console.error", but rather + // just rely on the actual error interrupting the test, we re-throw here. + {provide: ErrorHandler, useValue: ({handleError: (err: any) => { throw err; }})}, + ...providers + ], + }).compileComponents(); + + return TestBed.createComponent(component); +} + + +@Component({ + template: ` + + Label + + ` +}) +class MatInputWithId { + floatLabel: 'always' | 'auto' = 'auto'; +} + +@Component({ + template: `` +}) +class MatInputWithDisabled { + disabled: boolean; +} + +@Component({ + template: `` +}) +class MatInputWithRequired { + required: boolean; +} + +@Component({ + template: `` +}) +class MatInputWithType { + type: string; +} + +@Component({ + template: ` + + hello + + ` +}) +class MatInputLabelRequiredTestComponent { + hideRequiredMarker: boolean = false; + disabled: boolean = false; +} + +@Component({ + template: ` + + + ` +}) +class MatInputWithFormControl { + formControl = new FormControl(); +} + +@Component({ + template: `{{label}}` +}) +class MatInputHintLabel2TestController { + label: string = ''; +} + +@Component({ + template: `` +}) +class MatInputHintLabelTestController { + label: string = ''; +} + +@Component({template: ``}) +class MatInputInvalidTypeTestController { + t = 'file'; +} + +@Component({ + template: ` + + + World + ` +}) +class MatInputInvalidHint2TestController {} + +@Component({ + template: ` + + + Hello + World + ` +}) +class MatInputInvalidHintTestController {} + +@Component({ + template: ` + + + Hello + World + ` +}) +class MatInputMultipleHintTestController { + startId: string; + endId: string; +} + +@Component({ + template: ` + + + World + ` +}) +class MatInputMultipleHintMixedTestController {} + +@Component({ + template: ` + + + ` +}) +class MatInputDateTestController {} + +@Component({ + template: ` + + Label + + ` +}) +class MatInputTextTestController {} + +@Component({ + template: ` + + + ` +}) +class MatInputPasswordTestController {} + +@Component({ + template: ` + + + ` +}) +class MatInputNumberTestController {} + +@Component({ + template: ` + + + ` +}) +class MatInputZeroTestController { + value = 0; +} + +@Component({ + template: ` + + + ` +}) +class MatInputWithValueBinding { + value: string = 'Initial'; +} + +@Component({ + template: ` + + + + ` +}) +class MatInputWithStaticLabel {} + +@Component({ + template: ` + + Label + + ` +}) +class MatInputWithDynamicLabel { + shouldFloat: 'always' | 'auto' = 'always'; +} + +@Component({ + template: ` + + + ` +}) +class MatInputTextareaWithBindings { + rows: number = 4; + cols: number = 8; + wrap: string = 'hard'; +} + +@Component({ + template: `` +}) +class MatInputMissingMatInputTestController {} + +@Component({ + template: ` +
    + + + Please type something + This field is required + +
    + ` +}) +class MatInputWithFormErrorMessages { + @ViewChild('form') form: NgForm; + formControl = new FormControl('', Validators.required); + renderError = true; +} + +@Component({ + template: ` +
    + + + Please type something + This field is required + +
    + ` +}) +class MatInputWithCustomErrorStateMatcher { + formGroup = new FormGroup({ + name: new FormControl('', Validators.required) + }); + + errorState = false; + + customErrorStateMatcher = { + isErrorState: () => this.errorState + }; +} + +@Component({ + template: ` +
    + + + Please type something + This field is required + +
    + ` +}) +class MatInputWithFormGroupErrorMessages { + @ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective; + formGroup = new FormGroup({ + name: new FormControl('', Validators.required) + }); +} + +@Component({ + template: ` + +
    Prefix
    + +
    Suffix
    +
    + ` +}) +class MatInputWithPrefixAndSuffix {} + +@Component({ + template: ` + + + + ` +}) +class MatInputWithNgIf { + renderInput = true; +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + Label + + + ` +}) +class MatInputOnPush { + formControl = new FormControl(''); +} + +@Component({ + template: ` + + + + ` +}) +class MatInputWithReadonlyInput { + isReadonly = true; +} + +@Component({ + template: ` + + Label + + + ` +}) +class MatInputWithLabel {} + +@Component({ + template: ` + + + + ` +}) +class MatInputWithAppearance { + @ViewChild(MatFormField) formField: MatFormField; + appearance: MatFormFieldAppearance; +} + +@Component({ + template: ` + + + + ` +}) +class MatInputWithoutPlaceholder { +} + +@Component({ + template: ` + + Label + + ` +}) +class MatInputSelect { + disabled: boolean; + required: boolean; +} + +@Component({ + template: ` + + Form-field label + + ` +}) +class MatInputSelectWithNoLabelNoValue {} + +@Component({ + template: ` + + Label + + ` +}) +class MatInputSelectWithLabel {} + +@Component({ + template: ` + + Label + + ` +}) +class MatInputSelectWithInnerHtml {} + +@Component({ + template: ` + + + ` +}) +class MatInputWithCustomAccessor {} + +@Component({ + template: ` + + + ` +}) +class MatInputSelectWithoutOptions {} + + +/** Custom component that never has a value. Used for testing the `MAT_INPUT_VALUE_ACCESSOR`. */ +@Directive({ + selector: 'input[customInputAccessor]', + providers: [{ + provide: MAT_INPUT_VALUE_ACCESSOR, + useExisting: CustomMatInputAccessor + }] +}) +class CustomMatInputAccessor { + get value() { return this._value; } + set value(_value: any) {} + private _value = null; +} diff --git a/src/material-experimental/mdc-input/input.ts b/src/material-experimental/mdc-input/input.ts new file mode 100644 index 000000000000..236e68f39100 --- /dev/null +++ b/src/material-experimental/mdc-input/input.ts @@ -0,0 +1,46 @@ +/** + * @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 {Directive} from '@angular/core'; +import {MatFormFieldControl} from '@angular/material/form-field'; +import {MatInput as BaseMatInput} from '@angular/material/input'; + +// workaround until we have feature targeting for MDC text-field. At that +// point we can just use the actual "MatInput" class and apply the MDC text-field +// styles appropriately. + +@Directive({ + selector: `input[matInput], textarea[matInput], select[matNativeControl], + input[matNativeControl], textarea[matNativeControl]`, + exportAs: 'matInput', + host: { + 'class': 'mat-mdc-input-element mdc-text-field__input', + // The BaseMatInput parent class adds `mat-input-element` and `mat-form-field-autofill-control` + // to the CSS classlist, but this should not be added for this MDC equivalent input. + '[class.mat-form-field-autofill-control]': 'false', + '[class.mat-input-element]': 'false', + '[class.mat-input-server]': '_isServer', + '[class.mat-mdc-textarea-input]': '_isTextarea()', + // Native input properties that are overwritten by Angular inputs need to be synced with + // the native input element. Otherwise property bindings for those don't work. + '[id]': 'id', + '[disabled]': 'disabled', + '[required]': 'required', + '[attr.placeholder]': 'placeholder', + '[attr.readonly]': 'readonly && !_isNativeSelect || null', + '[attr.aria-describedby]': '_ariaDescribedby || null', + '[attr.aria-invalid]': 'errorState', + '[attr.aria-required]': 'required.toString()', + '(blur)': '_focusChanged(false)', + '(focus)': '_focusChanged(true)', + '(input)': '_onInput()', + }, + providers: [{provide: MatFormFieldControl, useExisting: MatInput}], +}) +export class MatInput extends BaseMatInput {} + diff --git a/src/material-experimental/mdc-input/module.ts b/src/material-experimental/mdc-input/module.ts new file mode 100644 index 000000000000..52830ce50dd2 --- /dev/null +++ b/src/material-experimental/mdc-input/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 {TextFieldModule} from '@angular/cdk/text-field'; +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; +import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatInput} from './input'; + +@NgModule({ + imports: [MatCommonModule, MatFormFieldModule], + exports: [MatInput, MatFormFieldModule, TextFieldModule, MatCommonModule], + declarations: [MatInput], +}) +export class MatInputModule {} diff --git a/src/material-experimental/mdc-input/public-api.ts b/src/material-experimental/mdc-input/public-api.ts new file mode 100644 index 000000000000..59ca77fabb0e --- /dev/null +++ b/src/material-experimental/mdc-input/public-api.ts @@ -0,0 +1,11 @@ +/** + * @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 {getMatInputUnsupportedTypeError, MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input'; +export {MatInput} from './input'; +export {MatInputModule} from './module'; diff --git a/src/material-experimental/mdc-input/testing/BUILD.bazel b/src/material-experimental/mdc-input/testing/BUILD.bazel new file mode 100644 index 000000000000..b441288bcbb1 --- /dev/null +++ b/src/material-experimental/mdc-input/testing/BUILD.bazel @@ -0,0 +1,48 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material-experimental/mdc-input/testing", + deps = [ + "//src/material/form-field/testing/control", + "//src/material/input/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["shared.spec.ts"], + ), + deps = [ + ":testing", + "//src/material-experimental/mdc-input", + "//src/material/input/testing:harness_tests_lib", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = [ + "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", + "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", + "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", + "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", + ], + deps = [ + ":unit_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) diff --git a/src/material-experimental/mdc-input/testing/index.ts b/src/material-experimental/mdc-input/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-input/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-input/testing/input-harness.spec.ts b/src/material-experimental/mdc-input/testing/input-harness.spec.ts new file mode 100644 index 000000000000..344d963b51bd --- /dev/null +++ b/src/material-experimental/mdc-input/testing/input-harness.spec.ts @@ -0,0 +1,7 @@ +import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {runHarnessTests} from '@angular/material/input/testing/shared.spec'; +import {MatInputHarness} from './index'; + +describe('MDC-based MatInputHarness', () => { + runHarnessTests(MatInputModule, MatInputHarness); +}); diff --git a/src/material-experimental/mdc-input/testing/public-api.ts b/src/material-experimental/mdc-input/testing/public-api.ts new file mode 100644 index 000000000000..bba575f10d58 --- /dev/null +++ b/src/material-experimental/mdc-input/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 {InputHarnessFilters, MatInputHarness} from '@angular/material/input/testing'; diff --git a/src/material-experimental/mdc-menu/_mdc-menu.scss b/src/material-experimental/mdc-menu/_mdc-menu.scss index 7e221dc5ee32..121a9039e7ea 100644 --- a/src/material-experimental/mdc-menu/_mdc-menu.scss +++ b/src/material-experimental/mdc-menu/_mdc-menu.scss @@ -1,8 +1,8 @@ -@import '@material/menu-surface/mixins'; -@import '@material/menu-surface/variables'; -@import '@material/list/mixins'; -@import '@material/list/variables'; -@import '@material/theme/functions'; +@import '@material/menu-surface/mixins.import'; +@import '@material/menu-surface/variables.import'; +@import '@material/list/mixins.import'; +@import '@material/list/variables.import'; +@import '@material/theme/functions.import'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-menu-theme-mdc($theme) { diff --git a/src/material-experimental/mdc-menu/menu-item.ts b/src/material-experimental/mdc-menu/menu-item.ts index a1be38303e13..16358c8dff8e 100644 --- a/src/material-experimental/mdc-menu/menu-item.ts +++ b/src/material-experimental/mdc-menu/menu-item.ts @@ -6,13 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {Component, ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu'; /** - * This directive is intended to be used inside an mat-menu tag. - * It exists mostly to set the role attribute. + * Single item inside of a `mat-menu`. Provides the menu item styling and accessibility treatment. */ @Component({ selector: '[mat-menu-item]', @@ -38,6 +36,4 @@ import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu'; ] }) export class MatMenuItem extends BaseMatMenuItem { - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-menu/menu.scss b/src/material-experimental/mdc-menu/menu.scss index 78735beb24a9..949db115c9fb 100644 --- a/src/material-experimental/mdc-menu/menu.scss +++ b/src/material-experimental/mdc-menu/menu.scss @@ -1,6 +1,6 @@ -@import '@material/menu-surface/mixins'; -@import '@material/list/mixins'; -@import '@material/list/variables'; +@import '@material/menu-surface/mixins.import'; +@import '@material/list/mixins.import'; +@import '@material/list/variables.import'; @import '../../material/core/style/menu-common'; @import '../../material/core/style/button-common'; @import '../../cdk/a11y/a11y'; @@ -23,7 +23,7 @@ // The CDK positioning uses flexbox to anchor the element, whereas MDC uses `position: absolute`. position: static; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } @@ -75,7 +75,7 @@ } } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { &.cdk-program-focused, &.cdk-keyboard-focused, &-highlighted { diff --git a/src/material-experimental/mdc-menu/menu.ts b/src/material-experimental/mdc-menu/menu.ts index 79922c3b0e30..11e90415b04f 100644 --- a/src/material-experimental/mdc-menu/menu.ts +++ b/src/material-experimental/mdc-menu/menu.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {Overlay, ScrollStrategy} from '@angular/cdk/overlay'; import { ChangeDetectionStrategy, @@ -71,7 +70,4 @@ export class MatMenu extends BaseMatMenu { // - should update the elevation when the same menu is opened at a different depth // - should not increase the elevation if the user specified a custom one } - - static ngAcceptInputType_overlapTrigger: BooleanInput; - static ngAcceptInputType_hasBackdrop: BooleanInput; } diff --git a/src/material-experimental/mdc-progress-bar/_mdc-progress-bar.scss b/src/material-experimental/mdc-progress-bar/_mdc-progress-bar.scss index dcacff151b5f..6aa5cbd0a709 100644 --- a/src/material-experimental/mdc-progress-bar/_mdc-progress-bar.scss +++ b/src/material-experimental/mdc-progress-bar/_mdc-progress-bar.scss @@ -1,5 +1,5 @@ -@import '@material/linear-progress/mixins'; -@import '@material/theme/functions'; +@import '@material/linear-progress/mixins.import'; +@import '@material/theme/functions.import'; @import '../mdc-helpers/mdc-helpers'; @mixin _mat-mdc-progress-bar-color($color) { diff --git a/src/material-experimental/mdc-progress-bar/progress-bar.scss b/src/material-experimental/mdc-progress-bar/progress-bar.scss index 1cc2c558d08c..cc147d464f91 100644 --- a/src/material-experimental/mdc-progress-bar/progress-bar.scss +++ b/src/material-experimental/mdc-progress-bar/progress-bar.scss @@ -1,4 +1,4 @@ -@import '@material/linear-progress/mixins'; +@import '@material/linear-progress/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @include mdc-linear-progress-core-styles($query: $mat-base-styles-without-animation-query); diff --git a/src/material-experimental/mdc-radio/radio.scss b/src/material-experimental/mdc-radio/radio.scss index f295d529ca75..bc6ceae94113 100644 --- a/src/material-experimental/mdc-radio/radio.scss +++ b/src/material-experimental/mdc-radio/radio.scss @@ -1,5 +1,5 @@ -@import '@material/radio/mixins'; -@import '@material/form-field/mixins'; +@import '@material/radio/mixins.import'; +@import '@material/form-field/mixins.import'; @import '../mdc-helpers/mdc-helpers'; diff --git a/src/material-experimental/mdc-select/module.ts b/src/material-experimental/mdc-select/module.ts index bf2bea9e0174..363f2400f179 100644 --- a/src/material-experimental/mdc-select/module.ts +++ b/src/material-experimental/mdc-select/module.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule], exports: [MatCommonModule], }) export class MatSelectModule { diff --git a/src/material-experimental/mdc-sidenav/module.ts b/src/material-experimental/mdc-sidenav/module.ts index 7df9f38cca66..120dd87e16a7 100644 --- a/src/material-experimental/mdc-sidenav/module.ts +++ b/src/material-experimental/mdc-sidenav/module.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule], exports: [MatCommonModule], }) export class MatSidenavModule { diff --git a/src/material-experimental/mdc-slide-toggle/_mdc-slide-toggle.scss b/src/material-experimental/mdc-slide-toggle/_mdc-slide-toggle.scss index 3eea1af678e8..c0d5731acb87 100644 --- a/src/material-experimental/mdc-slide-toggle/_mdc-slide-toggle.scss +++ b/src/material-experimental/mdc-slide-toggle/_mdc-slide-toggle.scss @@ -1,8 +1,8 @@ -@import '@material/switch/mixins'; -@import '@material/switch/variables'; -@import '@material/form-field/mixins'; -@import '@material/ripple/variables'; -@import '@material/theme/functions'; +@import '@material/switch/mixins.import'; +@import '@material/switch/variables.import'; +@import '@material/form-field/mixins.import'; +@import '@material/ripple/variables.import'; +@import '@material/theme/functions.import'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-slide-toggle-theme-mdc($theme) { @@ -17,7 +17,6 @@ @include mat-using-mdc-theme($theme) { $mdc-switch-baseline-theme-color: primary !global; - @include mdc-switch-without-ripple($query: $mat-theme-styles-query); @include mdc-form-field-core-styles($query: $mat-theme-styles-query); // MDC's switch doesn't support a `color` property. We add support @@ -27,6 +26,10 @@ background: $mdc-switch-toggled-off-ripple-color; } + &.mat-primary { + @include mdc-switch-without-ripple($query: $mat-theme-styles-query); + } + &.mat-accent { $mdc-switch-baseline-theme-color: secondary !global; @include mdc-switch-without-ripple($query: $mat-theme-styles-query); diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.html b/src/material-experimental/mdc-slide-toggle/slide-toggle.html index 7b251dc02d67..823510286e97 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.html +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.html @@ -1,6 +1,6 @@
    -
    +
    -
    diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss index 5a987f8d44f6..8d4f828bea6b 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss @@ -1,8 +1,8 @@ -@import '@material/switch/functions'; -@import '@material/switch/mixins'; -@import '@material/switch/variables'; -@import '@material/form-field/mixins'; -@import '@material/ripple/variables'; +@import '@material/switch/functions.import'; +@import '@material/switch/mixins.import'; +@import '@material/switch/variables.import'; +@import '@material/form-field/mixins.import'; +@import '@material/ripple/variables.import'; @import '../mdc-helpers/mdc-helpers'; @import '../../material/core/style/layout-common'; @import '../../cdk/a11y/a11y'; @@ -55,7 +55,7 @@ } -@include cdk-high-contrast { +@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 diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts index 7b684a345156..4107a43eaf43 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts @@ -13,6 +13,7 @@ describe('MDC-based MatSlideToggle without forms', () => { imports: [MatSlideToggleModule, BidiModule], declarations: [ SlideToggleBasic, + SlideToggleCheckedAndDisabledAttr, SlideToggleWithTabindexAttr, SlideToggleWithoutLabel, SlideToggleProjectedLabel, @@ -320,6 +321,22 @@ describe('MDC-based MatSlideToggle without forms', () => { .toBe(5, 'Expected tabIndex property to have been set based on the native attribute'); })); + it('should add the disabled class if disabled through attribute', () => { + const fixture = TestBed.createComponent(SlideToggleCheckedAndDisabledAttr); + fixture.detectChanges(); + + const switchEl = fixture.nativeElement.querySelector('.mdc-switch'); + expect(switchEl.classList).toContain('mdc-switch--disabled'); + }); + + it('should add the checked class if checked through attribute', () => { + const fixture = TestBed.createComponent(SlideToggleCheckedAndDisabledAttr); + fixture.detectChanges(); + + const switchEl = fixture.nativeElement.querySelector('.mdc-switch'); + expect(switchEl.classList).toContain('mdc-switch--checked'); + }); + it('should remove the tabindex from the host element', fakeAsync(() => { const fixture = TestBed.createComponent(SlideToggleWithTabindexAttr); @@ -772,6 +789,11 @@ class SlideToggleWithModel { isDisabled = false; } +@Component({ + template: `Label` +}) +class SlideToggleCheckedAndDisabledAttr {} + @Component({ template: ` diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.ts b/src/material-experimental/mdc-slide-toggle/slide-toggle.ts index 553615eb759d..7392b4a0a6e9 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.ts +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.ts @@ -68,9 +68,9 @@ export class MatSlideToggleChange { '[attr.tabindex]': 'null', '[attr.aria-label]': 'null', '[attr.aria-labelledby]': 'null', - '[class.mat-primary]': 'color == "primary"', - '[class.mat-accent]': 'color == "accent"', - '[class.mat-warn]': 'color == "warn"', + '[class.mat-primary]': 'color === "primary"', + '[class.mat-accent]': 'color !== "primary" && color !== "warn"', + '[class.mat-warn]': 'color === "warn"', '[class.mat-mdc-slide-toggle-focused]': '_focused', '[class.mat-mdc-slide-toggle-checked]': 'checked', '[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"', @@ -91,26 +91,18 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe private _checked: boolean = false; private _foundation: MDCSwitchFoundation; private _adapter: MDCSwitchAdapter = { - addClass: (className) => { - this._toggleClass(className, true); - }, - removeClass: (className) => { - this._toggleClass(className, false); - }, - setNativeControlChecked: (checked) => { - this._checked = checked; - }, - setNativeControlDisabled: (disabled) => { - this._disabled = disabled; - }, + addClass: className => this._switchElement.nativeElement.classList.add(className), + removeClass: className => this._switchElement.nativeElement.classList.remove(className), + setNativeControlChecked: checked => this._checked = checked, + setNativeControlDisabled: disabled => this._disabled = disabled, + setNativeControlAttr: (name, value) => { + this._inputElement.nativeElement.setAttribute(name, value); + } }; /** Whether the slide toggle is currently focused. */ _focused: boolean; - /** The set of classes that should be applied to the native input. */ - _classes: {[key: string]: boolean} = {'mdc-switch': true}; - /** Configuration for the underlying ripple. */ _rippleAnimation: RippleAnimationConfig = { enterDuration: numbers.DEACTIVATION_TIMEOUT_MS, @@ -206,6 +198,9 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe /** Reference to the underlying input element. */ @ViewChild('input') _inputElement: ElementRef; + /** Reference to the MDC switch element. */ + @ViewChild('switch') _switchElement: ElementRef; + constructor(private _changeDetectorRef: ChangeDetectorRef, @Attribute('tabindex') tabIndex: string, @Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS) @@ -311,12 +306,6 @@ export class MatSlideToggle implements ControlValueAccessor, AfterViewInit, OnDe }); } - /** Toggles a class on the switch element. */ - private _toggleClass(cssClass: string, active: boolean) { - this._classes[cssClass] = active; - this._changeDetectorRef.markForCheck(); - } - static ngAcceptInputType_tabIndex: NumberInput; static ngAcceptInputType_required: BooleanInput; static ngAcceptInputType_checked: BooleanInput; diff --git a/src/material-experimental/mdc-slider/_mdc-slider.scss b/src/material-experimental/mdc-slider/_mdc-slider.scss index 34cf4cc1ace7..4aac8cc9783d 100644 --- a/src/material-experimental/mdc-slider/_mdc-slider.scss +++ b/src/material-experimental/mdc-slider/_mdc-slider.scss @@ -1,5 +1,5 @@ @import '../mdc-helpers/mdc-helpers'; -@import '@material/slider/mixins'; +@import '@material/slider/mixins.import'; @mixin mat-slider-theme-mdc($theme) { @include mat-using-mdc-theme($theme) { diff --git a/src/material-experimental/mdc-slider/slider.scss b/src/material-experimental/mdc-slider/slider.scss index 2f8703d29993..f73e90be7aee 100644 --- a/src/material-experimental/mdc-slider/slider.scss +++ b/src/material-experimental/mdc-slider/slider.scss @@ -1,4 +1,4 @@ -@import '@material/slider/mixins'; +@import '@material/slider/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @import '../../cdk/a11y/a11y'; @@ -26,7 +26,7 @@ $mat-slider-horizontal-margin: 8px !default; width: auto; min-width: $mat-slider-min-size - (2 * $mat-slider-horizontal-margin); - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { // The slider track isn't visible in high contrast mode so we work // around it by setting an outline and removing the height to make it look solid. .mdc-slider__track-container { diff --git a/src/material-experimental/mdc-snackbar/BUILD.bazel b/src/material-experimental/mdc-snackbar/BUILD.bazel index ff82a2799da7..7b62596662b3 100644 --- a/src/material-experimental/mdc-snackbar/BUILD.bazel +++ b/src/material-experimental/mdc-snackbar/BUILD.bazel @@ -1,7 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library") -load("//tools:defaults.bzl", "ng_module") +load("//tools:defaults.bzl", "ng_module", "sass_binary", "sass_library") ng_module( name = "mdc-snackbar", @@ -13,14 +12,13 @@ ng_module( module_name = "@angular/material-experimental/mdc-snackbar", deps = [ "//src/material/core", - "@npm//@angular/common", "@npm//@angular/core", "@npm//material-components-web", ], ) sass_library( - name = "snackbar_scss_lib", + name = "mdc_snackbar_scss_lib", srcs = glob(["**/_*.scss"]), deps = [ "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", diff --git a/src/material-experimental/mdc-snackbar/module.ts b/src/material-experimental/mdc-snackbar/module.ts index 807fdc9cf054..1b590bf96ee5 100644 --- a/src/material-experimental/mdc-snackbar/module.ts +++ b/src/material-experimental/mdc-snackbar/module.ts @@ -6,13 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; import {MatSnackbar} from './snackbar'; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule], exports: [MatSnackbar, MatCommonModule], declarations: [MatSnackbar], }) diff --git a/src/material-experimental/mdc-table/BUILD.bazel b/src/material-experimental/mdc-table/BUILD.bazel index e2f0a16b9037..2fce9128bf49 100644 --- a/src/material-experimental/mdc-table/BUILD.bazel +++ b/src/material-experimental/mdc-table/BUILD.bazel @@ -22,7 +22,6 @@ ng_module( deps = [ "//src/cdk/table", "//src/material/core", - "@npm//@angular/common", "@npm//@angular/core", "@npm//material-components-web", ], diff --git a/src/material-experimental/mdc-table/_mdc-table.scss b/src/material-experimental/mdc-table/_mdc-table.scss index f78088bf9ef4..8039a3e6d69f 100644 --- a/src/material-experimental/mdc-table/_mdc-table.scss +++ b/src/material-experimental/mdc-table/_mdc-table.scss @@ -1,5 +1,5 @@ @import '../mdc-helpers/mdc-helpers'; -@import '@material/data-table/mixins'; +@import '@material/data-table/mixins.import'; @mixin mat-table-theme-mdc($theme) { @include mat-using-mdc-theme($theme) { diff --git a/src/material-experimental/mdc-table/cell.ts b/src/material-experimental/mdc-table/cell.ts index 7c970424dd13..98b5ac48c63b 100644 --- a/src/material-experimental/mdc-table/cell.ts +++ b/src/material-experimental/mdc-table/cell.ts @@ -63,7 +63,6 @@ export class MatColumnDef extends CdkColumnDef { @Input('matColumnDef') name: string; static ngAcceptInputType_sticky: BooleanInput; - static ngAcceptInputType_stickyEnd: BooleanInput; } /** Header cell template container that adds the right classes and role. */ diff --git a/src/material-experimental/mdc-table/module.ts b/src/material-experimental/mdc-table/module.ts index 4b78cccc69f8..af5da1ef19d8 100644 --- a/src/material-experimental/mdc-table/module.ts +++ b/src/material-experimental/mdc-table/module.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; import {MatTable} from './table'; @@ -54,7 +53,7 @@ const EXPORTED_DECLARATIONS = [ ]; @NgModule({ - imports: [MatCommonModule, CommonModule, CdkTableModule], + imports: [MatCommonModule, CdkTableModule], exports: EXPORTED_DECLARATIONS, declarations: EXPORTED_DECLARATIONS, }) diff --git a/src/material-experimental/mdc-table/table.scss b/src/material-experimental/mdc-table/table.scss index 7cc09b787aa3..2b0af8558604 100644 --- a/src/material-experimental/mdc-table/table.scss +++ b/src/material-experimental/mdc-table/table.scss @@ -1,4 +1,4 @@ -@import '@material/data-table/mixins'; +@import '@material/data-table/mixins.import'; @import '../mdc-helpers/mdc-helpers'; @include mdc-data-table-core-styles($query: $mat-base-styles-without-animation-query); diff --git a/src/material-experimental/mdc-table/table.ts b/src/material-experimental/mdc-table/table.ts index c777d5638e40..daeb200e8e84 100644 --- a/src/material-experimental/mdc-table/table.ts +++ b/src/material-experimental/mdc-table/table.ts @@ -8,7 +8,6 @@ import {ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation} from '@angular/core'; import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table'; -import {BooleanInput} from '@angular/cdk/coercion'; @Component({ selector: 'table[mat-table]', @@ -28,8 +27,6 @@ export class MatTable extends CdkTable implements OnInit { /** Overrides the sticky CSS class set by the `CdkTable`. */ protected stickyCssClass = 'mat-mdc-table-sticky'; - static ngAcceptInputType_multiTemplateDataRows: BooleanInput; - // After ngOnInit, the `CdkTable` has created and inserted the table sections (thead, tbody, // tfoot). MDC requires the `mdc-data-table__content` class to be added to the body. ngOnInit() { diff --git a/src/material-experimental/mdc-tabs/BUILD.bazel b/src/material-experimental/mdc-tabs/BUILD.bazel index 4a239882718c..6bbd39b79142 100644 --- a/src/material-experimental/mdc-tabs/BUILD.bazel +++ b/src/material-experimental/mdc-tabs/BUILD.bazel @@ -18,7 +18,6 @@ ng_module( exclude = ["**/*.spec.ts"], ), assets = [ - ":tabs_scss", ":tab-body.css", ":tab-header.css", ":tab-group.css", @@ -53,16 +52,6 @@ sass_library( ], ) -sass_binary( - name = "tabs_scss", - src = "_mdc-tabs.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", - ], -) - sass_binary( name = "mdc_tab_body_scss", src = "tab-body.scss", diff --git a/src/material-experimental/mdc-tabs/_mdc-tabs.scss b/src/material-experimental/mdc-tabs/_mdc-tabs.scss index 3fb930d88d83..3a658d6eb17d 100644 --- a/src/material-experimental/mdc-tabs/_mdc-tabs.scss +++ b/src/material-experimental/mdc-tabs/_mdc-tabs.scss @@ -1,7 +1,7 @@ -@import '@material/theme/functions'; -@import '@material/tab-indicator/mixins'; -@import '@material/tab/mixins'; -@import '@material/tab/variables'; +@import '@material/theme/functions.import'; +@import '@material/tab-indicator/mixins.import'; +@import '@material/tab/mixins.import'; +@import '@material/tab/variables.import'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-tabs-theme-mdc($theme) { @@ -9,8 +9,14 @@ // variables to their original values and prevent unintended side effects from using this mixin. $orig-mdc-tab-text-label-color-active: $mdc-tab-text-label-color-active; $orig-mdc-tab-icon-color-active: $mdc-tab-icon-color-active; + $orig-mdc-tab-text-label-color-default: $mdc-tab-text-label-color-default; @include mat-using-mdc-theme($theme) { + // This value is the same as MDC's default, but MDC defines it once inside + // a variables file which means that we can't override it with our own palette. + $mdc-tab-text-label-color-default: + rgba(mdc-theme-prop-value(on-surface), $mdc-tab-text-label-opacity) !global; + @include _mat-mdc-tabs-palette-styles($mdc-tab-text-label-color-active); .mat-mdc-tab-group, .mat-mdc-tab-nav-bar { @@ -49,6 +55,7 @@ // Restore original values of MDC global variables. $mdc-tab-text-label-color-active: $orig-mdc-tab-text-label-color-active !global; $mdc-tab-icon-color-active: $orig-mdc-tab-icon-color-active !global; + $mdc-tab-text-label-color-default: $orig-mdc-tab-text-label-color-default !global; } @mixin _mat-mdc-tabs-background($background-color, $foreground-color) { diff --git a/src/material-experimental/mdc-tabs/_tabs-common.scss b/src/material-experimental/mdc-tabs/_tabs-common.scss index 608b6cf82ec9..461c41710ae4 100644 --- a/src/material-experimental/mdc-tabs/_tabs-common.scss +++ b/src/material-experimental/mdc-tabs/_tabs-common.scss @@ -1,6 +1,6 @@ -@import '@material/ripple/variables'; -@import '@material/tab/variables'; -@import '@material/tab/mixins'; +@import '@material/ripple/variables.import'; +@import '@material/tab/variables.import'; +@import '@material/tab/mixins.import'; @import '../../material/core/style/variables'; @import '../../material/core/style/noop-animation'; @import '../../material/core/style/vendor-prefixes'; diff --git a/src/material-experimental/mdc-tabs/tab-group.scss b/src/material-experimental/mdc-tabs/tab-group.scss index b37e676b3dd0..5402857f01d6 100644 --- a/src/material-experimental/mdc-tabs/tab-group.scss +++ b/src/material-experimental/mdc-tabs/tab-group.scss @@ -1,5 +1,5 @@ -@import '@material/tab/mixins'; -@import '@material/ripple/variables'; +@import '@material/tab/mixins.import'; +@import '@material/ripple/variables.import'; @import '../../material/core/style/variables'; @import '../../material/core/style/noop-animation'; @import '../mdc-helpers/mdc-helpers'; diff --git a/src/material-experimental/mdc-tabs/tab-group.ts b/src/material-experimental/mdc-tabs/tab-group.ts index c4130f04e7b5..c7d1e4e7761a 100644 --- a/src/material-experimental/mdc-tabs/tab-group.ts +++ b/src/material-experimental/mdc-tabs/tab-group.ts @@ -77,8 +77,5 @@ export class MatTabGroup extends _MatTabGroupBase { } static ngAcceptInputType_fitInkBarToContent: BooleanInput; - static ngAcceptInputType_dynamicHeight: BooleanInput; static ngAcceptInputType_animationDuration: NumberInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-tabs/tab-header.html b/src/material-experimental/mdc-tabs/tab-header.html index 115739bee519..64dd17e7cf6a 100644 --- a/src/material-experimental/mdc-tabs/tab-header.html +++ b/src/material-experimental/mdc-tabs/tab-header.html @@ -6,7 +6,7 @@ [matRippleDisabled]="_disableScrollBefore || disableRipple" [class.mat-mdc-tab-header-pagination-disabled]="_disableScrollBefore" (click)="_handlePaginatorClick('before')" - (mousedown)="_handlePaginatorPress('before')" + (mousedown)="_handlePaginatorPress('before', $event)" (touchend)="_stopInterval()">
    @@ -31,7 +31,7 @@ mat-ripple [matRippleDisabled]="_disableScrollAfter || disableRipple" [class.mat-mdc-tab-header-pagination-disabled]="_disableScrollAfter" - (mousedown)="_handlePaginatorPress('after')" + (mousedown)="_handlePaginatorPress('after', $event)" (click)="_handlePaginatorClick('after')" (touchend)="_stopInterval()">
    diff --git a/src/material-experimental/mdc-tabs/tab-header.scss b/src/material-experimental/mdc-tabs/tab-header.scss index 1c4cd096a51f..2ac63810ea7c 100644 --- a/src/material-experimental/mdc-tabs/tab-header.scss +++ b/src/material-experimental/mdc-tabs/tab-header.scss @@ -1,4 +1,4 @@ -@import '@material/tab-indicator/mixins'; +@import '@material/tab-indicator/mixins.import'; @import '../../material/core/style/noop-animation'; @import '../mdc-helpers/mdc-helpers'; @import './tabs-common'; diff --git a/src/material-experimental/mdc-tabs/tab-header.spec.ts b/src/material-experimental/mdc-tabs/tab-header.spec.ts index b684efb045d7..a80fbee2cf64 100644 --- a/src/material-experimental/mdc-tabs/tab-header.spec.ts +++ b/src/material-experimental/mdc-tabs/tab-header.spec.ts @@ -7,6 +7,7 @@ import { dispatchKeyboardEvent, createKeyboardEvent, dispatchEvent, + createMouseEvent, } from '@angular/cdk/testing/private'; import {CommonModule} from '@angular/common'; import {Component, ViewChild} from '@angular/core'; @@ -458,6 +459,16 @@ describe('MDC-based MatTabHeader', () => { expect(header.scrollDistance).toBe(previousDistance); })); + it('should not scroll when pressing the right mouse button', fakeAsync(() => { + expect(header.scrollDistance).toBe(0, 'Expected to start off not scrolled.'); + + dispatchEvent(nextButton, createMouseEvent('mousedown', undefined, undefined, 2)); + fixture.detectChanges(); + tick(3000); + + expect(header.scrollDistance).toBe(0, 'Expected not to have scrolled after a while.'); + })); + /** * Asserts that auto scrolling using the next button works. * @param startEventName Name of the event that is supposed to start the scrolling. diff --git a/src/material-experimental/mdc-tabs/tab-header.ts b/src/material-experimental/mdc-tabs/tab-header.ts index 063386b66db7..1a3f710cbc79 100644 --- a/src/material-experimental/mdc-tabs/tab-header.ts +++ b/src/material-experimental/mdc-tabs/tab-header.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput, NumberInput} from '@angular/cdk/coercion'; +import {BooleanInput} from '@angular/cdk/coercion'; import { ChangeDetectionStrategy, Component, @@ -75,5 +75,4 @@ export class MatTabHeader extends _MatTabHeaderBase implements AfterContentInit } static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } diff --git a/src/material-experimental/mdc-tabs/tab-label-wrapper.ts b/src/material-experimental/mdc-tabs/tab-label-wrapper.ts index a63944b5a820..113a21b7f70a 100644 --- a/src/material-experimental/mdc-tabs/tab-label-wrapper.ts +++ b/src/material-experimental/mdc-tabs/tab-label-wrapper.ts @@ -55,5 +55,4 @@ export class MatTabLabelWrapper extends BaseMatTabLabelWrapper } static ngAcceptInputType_fitInkBarToContent: BooleanInput; - static ngAcceptInputType_disabled: BooleanInput; } 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 5c203f41c47c..50c8940eaded 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 @@ -1,4 +1,4 @@ -@import '@material/tab/mixins'; +@import '@material/tab/mixins.import'; @import '../../../material/core/style/variables'; @import '../../mdc-helpers/mdc-helpers'; @import '../tabs-common'; diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.html b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.html index 8b77be104601..84e155b3bde9 100644 --- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.html +++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.html @@ -5,7 +5,7 @@ mat-ripple [matRippleDisabled]="_disableScrollBefore || disableRipple" [class.mat-mdc-tab-header-pagination-disabled]="_disableScrollBefore" (click)="_handlePaginatorClick('before')" - (mousedown)="_handlePaginatorPress('before')" + (mousedown)="_handlePaginatorPress('before', $event)" (touchend)="_stopInterval()">
    @@ -24,7 +24,7 @@ aria-hidden="true" mat-ripple [matRippleDisabled]="_disableScrollAfter || disableRipple" [class.mat-mdc-tab-header-pagination-disabled]="_disableScrollAfter" - (mousedown)="_handlePaginatorPress('after')" + (mousedown)="_handlePaginatorPress('after', $event)" (click)="_handlePaginatorClick('after')" (touchend)="_stopInterval()">
    diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.scss b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.scss index 3fa3c7997a0b..054c0bf808ae 100644 --- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.scss +++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.scss @@ -1,4 +1,4 @@ -@import '@material/tab/mixins'; +@import '@material/tab/mixins.import'; @import '../tabs-common'; @import '../../../material/core/style/variables'; @import '../../mdc-helpers/mdc-helpers'; diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts index 2b253c4a179b..d008b850f756 100644 --- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts @@ -36,7 +36,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {ViewportRuler} from '@angular/cdk/scrolling'; import {Platform} from '@angular/cdk/platform'; import {MatInkBar, MatInkBarItem, MatInkBarFoundation} from '../ink-bar'; -import {BooleanInput, coerceBooleanProperty, NumberInput} from '@angular/cdk/coercion'; +import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {BehaviorSubject, Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; @@ -104,7 +104,6 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit { static ngAcceptInputType_fitInkBarToContent: BooleanInput; static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } /** @@ -156,7 +155,4 @@ export class MatTabLink extends _MatTabLinkBase implements MatInkBarItem, OnInit super.ngOnDestroy(); this._foundation.destroy(); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material-experimental/mdc-tabs/tab.ts b/src/material-experimental/mdc-tabs/tab.ts index be20bfa5dcd9..6bb2134b8c83 100644 --- a/src/material-experimental/mdc-tabs/tab.ts +++ b/src/material-experimental/mdc-tabs/tab.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import { ChangeDetectionStrategy, Component, @@ -39,6 +38,4 @@ export class MatTab extends BaseMatTab { /** Content for the tab label given by ``. */ @ContentChild(MatTabLabel) templateLabel: MatTabLabel; - - static ngAcceptInputType_disabled: BooleanInput; } diff --git a/src/material-experimental/mdc-theming/BUILD.bazel b/src/material-experimental/mdc-theming/BUILD.bazel index 6573f9871a90..21e4bfff96a2 100644 --- a/src/material-experimental/mdc-theming/BUILD.bazel +++ b/src/material-experimental/mdc-theming/BUILD.bazel @@ -2,6 +2,14 @@ package(default_visibility = ["//visibility:public"]) load("//tools:defaults.bzl", "sass_binary", "sass_library") +filegroup( + name = "mdc-theming", + srcs = [ + ":all_themes", + ":indigo_pink_prebuilt", + ], +) + sass_library( name = "all_themes", srcs = [ @@ -12,6 +20,8 @@ sass_library( "//src/material-experimental/mdc-card:mdc_card_scss_lib", "//src/material-experimental/mdc-checkbox:mdc_checkbox_scss_lib", "//src/material-experimental/mdc-chips:mdc_chips_scss_lib", + "//src/material-experimental/mdc-form-field:mdc_form_field_scss_lib", + "//src/material-experimental/mdc-input:mdc_input_scss_lib", "//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", diff --git a/src/material-experimental/mdc-theming/_all-theme.scss b/src/material-experimental/mdc-theming/_all-theme.scss index 9fae2830013f..d1ad30ee87e9 100644 --- a/src/material-experimental/mdc-theming/_all-theme.scss +++ b/src/material-experimental/mdc-theming/_all-theme.scss @@ -9,6 +9,8 @@ @import '../mdc-tabs/mdc-tabs'; @import '../mdc-table/mdc-table'; @import '../mdc-progress-bar/mdc-progress-bar'; +@import '../mdc-input/mdc-input'; +@import '../mdc-form-field/mdc-form-field'; @mixin angular-material-theme-mdc($theme) { @include mat-button-theme-mdc($theme); @@ -23,6 +25,8 @@ @include mat-radio-theme-mdc($theme); @include mat-slide-toggle-theme-mdc($theme); @include mat-table-theme-mdc($theme); + @include mat-form-field-theme-mdc($theme); + @include mat-input-theme-mdc($theme); // TODO(andrewjs): Add this back when MDC syncs their slider code into Google-internal code // @include mat-slider-theme-mdc($theme); @include mat-tabs-theme-mdc($theme); diff --git a/src/material-experimental/mdc-typography/BUILD.bazel b/src/material-experimental/mdc-typography/BUILD.bazel index 1be4b16b315f..22640e2ed818 100644 --- a/src/material-experimental/mdc-typography/BUILD.bazel +++ b/src/material-experimental/mdc-typography/BUILD.bazel @@ -2,6 +2,11 @@ package(default_visibility = ["//visibility:public"]) load("//tools:defaults.bzl", "sass_library") +filegroup( + name = "mdc-typography", + srcs = [":all_typography"], +) + sass_library( name = "all_typography", srcs = [ @@ -12,6 +17,8 @@ sass_library( "//src/material-experimental/mdc-card:mdc_card_scss_lib", "//src/material-experimental/mdc-checkbox:mdc_checkbox_scss_lib", "//src/material-experimental/mdc-chips:mdc_chips_scss_lib", + "//src/material-experimental/mdc-form-field:mdc_form_field_scss_lib", + "//src/material-experimental/mdc-input:mdc_input_scss_lib", "//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", diff --git a/src/material-experimental/mdc-typography/_all-typography.scss b/src/material-experimental/mdc-typography/_all-typography.scss index 849d1bb54426..8edfe99ccb10 100644 --- a/src/material-experimental/mdc-typography/_all-typography.scss +++ b/src/material-experimental/mdc-typography/_all-typography.scss @@ -10,6 +10,8 @@ @import '../mdc-tabs/mdc-tabs'; @import '../mdc-table/mdc-table'; @import '../mdc-progress-bar/mdc-progress-bar'; +@import '../mdc-input/mdc-input'; +@import '../mdc-form-field/mdc-form-field'; @mixin angular-material-typography-mdc($config: null) { @if $config == null { @@ -30,4 +32,6 @@ @include mat-tabs-typography-mdc($config); @include mat-table-typography-mdc($config); @include mat-progress-bar-typography-mdc($config); + @include mat-input-typography-mdc($config); + @include mat-form-field-typography-mdc($config); } diff --git a/src/material-experimental/mdc_require_config.js b/src/material-experimental/mdc_require_config.js index 37cd8f410522..bff597ceb6a1 100644 --- a/src/material-experimental/mdc_require_config.js +++ b/src/material-experimental/mdc_require_config.js @@ -31,7 +31,7 @@ require.config({ '@material/tab-indicator': '/base/npm/node_modules/@material/tab-indicator/dist/mdc.tabIndicator', '@material/tab-scroller': '/base/npm/node_modules/@material/tab-scroller/dist/mdc.tabScroller', '@material/data-table': '/base/npm/node_modules/@material/data-table/dist/mdc.dataTable', - '@material/text-field': '/base/npm/node_modules/@material/textfield/dist/mdc.textField', + '@material/textfield': '/base/npm/node_modules/@material/textfield/dist/mdc.textfield', '@material/top-app-bar': '/base/npm/node_modules/@material/top-app-bar/dist/mdc.topAppBar', } }); diff --git a/src/material-experimental/popover-edit/_popover-edit.scss b/src/material-experimental/popover-edit/_popover-edit.scss index 55f204dd6702..f9c11b2f8c0a 100644 --- a/src/material-experimental/popover-edit/_popover-edit.scss +++ b/src/material-experimental/popover-edit/_popover-edit.scss @@ -81,7 +81,7 @@ display: block; padding: 16px 24px; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { // Note that normally we use 1px for high contrast outline, however here we use 3, // because the popover is rendered on top of a table which already has some borders // and doesn't have a backdrop. The thicker outline makes it easier to differentiate. @@ -99,7 +99,8 @@ margin: 0; } - [mat-edit-content] { + [mat-edit-content], + [mat-edit-fill] { display: block; mat-form-field { @@ -111,6 +112,20 @@ padding-top: 0; } } + + // Make mat-selection-lists inside of the look more like mat-select popups. + mat-selection-list { + max-height: 256px; // Same as mat-select. + overflow-y: auto; + } + } + + [mat-edit-fill] { + margin: -16px -24px; + + mat-selection-list:first-child { + padding-top: 0; + } } [mat-edit-actions] { @@ -119,6 +134,10 @@ flex-wrap: wrap; justify-content: flex-end; margin: 8px -16px -8px; + + [mat-edit-fill] + & { + margin-top: 16px; + } } } diff --git a/src/material-experimental/popover-edit/lens-directives.ts b/src/material-experimental/popover-edit/lens-directives.ts index b15e09dbde5d..2f6c07dad613 100644 --- a/src/material-experimental/popover-edit/lens-directives.ts +++ b/src/material-experimental/popover-edit/lens-directives.ts @@ -48,11 +48,6 @@ export class MatEditRevert extends CdkEditRevert { } /** Closes the lens on click. */ -@Directive({ - selector: 'button[matEditClose]', - host: { - 'type': 'button', // Prevents accidental form submits. - } -}) +@Directive({selector: '[matEditClose]'}) export class MatEditClose extends CdkEditClose { } diff --git a/src/material-experimental/popover-edit/popover-edit-module.ts b/src/material-experimental/popover-edit/popover-edit-module.ts index a1046b2f8b95..b59d7bd81321 100644 --- a/src/material-experimental/popover-edit/popover-edit-module.ts +++ b/src/material-experimental/popover-edit/popover-edit-module.ts @@ -7,7 +7,6 @@ */ import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; import {CdkEditable, CdkPopoverEditModule} from '@angular/cdk-experimental/popover-edit'; import { MatPopoverEdit, @@ -34,7 +33,6 @@ const EXPORTED_DECLARATIONS = [ @NgModule({ imports: [ CdkPopoverEditModule, - CommonModule, ], exports: [ ...EXPORTED_DECLARATIONS, diff --git a/src/material-experimental/popover-edit/popover-edit.spec.ts b/src/material-experimental/popover-edit/popover-edit.spec.ts index f0b93d326ac9..268cdbc0489f 100644 --- a/src/material-experimental/popover-edit/popover-edit.spec.ts +++ b/src/material-experimental/popover-edit/popover-edit.spec.ts @@ -50,7 +50,11 @@ const CELL_TEMPLATE = ` `; -const POPOVER_EDIT_DIRECTIVE_NAME = `[matPopoverEdit]="nameEdit" [matPopoverEditColspan]="colspan"`; +const POPOVER_EDIT_DIRECTIVE_NAME = ` + [matPopoverEdit]="nameEdit" + [matPopoverEditColspan]="colspan" + [matPopoverEditDisabled]="nameEditDisabled" + `; const POPOVER_EDIT_DIRECTIVE_WEIGHT = `[matPopoverEdit]="weightEdit" matPopoverEditTabOut`; @@ -65,6 +69,7 @@ abstract class BaseTestComponent { preservedValues = new FormValueContainer(); + nameEditDisabled = false; ignoreSubmitUnlessValid = true; clickOutBehavior: PopoverEditClickOutBehavior = 'close'; colspan: CdkPopoverEditColspan = {}; @@ -308,9 +313,7 @@ describe('Material Popover Edit', () => { expect(component.hoverContentStateForRow(rows.length - 1)) .toBe(HoverContentState.FOCUSABLE); })); - }); - describe('triggering edit', () => { it('shows and hides on-hover content only after a delay', fakeAsync(() => { const [row0, row1] = component.getRows(); row0.dispatchEvent(new Event('mouseover', {bubbles: true})); @@ -410,11 +413,35 @@ describe('Material Popover Edit', () => { expect(component.lensIsOpen()).toBe(true); clearLeftoverTimers(); })); + + it('does not trigger edit when disabled', fakeAsync(() => { + component.nameEditDisabled = true; + fixture.detectChanges(); + + // Uses Enter to open the lens. + component.openLens(); + + expect(component.lensIsOpen()).toBe(false); + clearLeftoverTimers(); + })); }); describe('focus manipulation', () => { const getRowCells = () => component.getRows().map(getCells); + describe('tabindex', () => { + it('sets tabindex to 0 on editable cells', () => { + expect(component.getEditCell().getAttribute('tabindex')).toBe('0'); + }); + + it('unsets tabindex to 0 on disabled cells', () => { + component.nameEditDisabled = true; + fixture.detectChanges(); + + expect(component.getEditCell().hasAttribute('tabindex')).toBe(false); + }); + }); + describe('arrow keys', () => { const dispatchKey = (cell: HTMLElement, keyCode: number) => dispatchKeyboardEvent(cell, 'keydown', keyCode, undefined, cell); diff --git a/src/material-experimental/popover-edit/table-directives.ts b/src/material-experimental/popover-edit/table-directives.ts index ec7e7df3f298..0d402075247f 100644 --- a/src/material-experimental/popover-edit/table-directives.ts +++ b/src/material-experimental/popover-edit/table-directives.ts @@ -16,15 +16,16 @@ import { } from '@angular/cdk-experimental/popover-edit'; const POPOVER_EDIT_HOST_BINDINGS = { - 'tabIndex': '0', + '[attr.tabindex]': 'disabled ? null : 0', 'class': 'mat-popover-edit-cell', - '[attr.aria-haspopup]': 'true', + '[attr.aria-haspopup]': '!disabled', }; const POPOVER_EDIT_INPUTS = [ 'template: matPopoverEdit', 'context: matPopoverEditContext', 'colspan: matPopoverEditColspan', + 'disabled: matPopoverEditDisabled', ]; const EDIT_PANE_CLASS = 'mat-edit-pane'; diff --git a/src/material/BUILD.bazel b/src/material/BUILD.bazel index 230ea8a24085..00182c4542b3 100644 --- a/src/material/BUILD.bazel +++ b/src/material/BUILD.bazel @@ -53,7 +53,7 @@ ng_package( ] + MATERIAL_SCSS_LIBS, entry_point = ":index.ts", entry_point_name = "material", - packages = ["//src/material/schematics:npm_package"], + nested_packages = ["//src/material/schematics:npm_package"], tags = ["release-package"], deps = MATERIAL_TARGETS + MATERIAL_TESTING_TARGETS, ) diff --git a/src/material/autocomplete/autocomplete.scss b/src/material/autocomplete/autocomplete.scss index f677703ba2b0..2f25c0e4e7cd 100644 --- a/src/material/autocomplete/autocomplete.scss +++ b/src/material/autocomplete/autocomplete.scss @@ -38,7 +38,7 @@ $mat-autocomplete-panel-border-radius: 4px !default; margin-top: -1px; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/badge/_badge-theme.scss b/src/material/badge/_badge-theme.scss index 3d3d5871a0e5..ee5196b42462 100644 --- a/src/material/badge/_badge-theme.scss +++ b/src/material/badge/_badge-theme.scss @@ -98,7 +98,7 @@ $mat-badge-large-size: $mat-badge-default-size + 6; color: mat-color($primary, default-contrast); background: mat-color($primary); - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; border-radius: 0; } diff --git a/src/material/bottom-sheet/BUILD.bazel b/src/material/bottom-sheet/BUILD.bazel index bd251db825a8..bafbaea8ecf6 100644 --- a/src/material/bottom-sheet/BUILD.bazel +++ b/src/material/bottom-sheet/BUILD.bazel @@ -29,7 +29,6 @@ ng_module( "//src/cdk/portal", "//src/material/core", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/material/bottom-sheet/bottom-sheet-container.scss b/src/material/bottom-sheet/bottom-sheet-container.scss index 7467b4a938bb..a84a36659da8 100644 --- a/src/material/bottom-sheet/bottom-sheet-container.scss +++ b/src/material/bottom-sheet/bottom-sheet-container.scss @@ -17,7 +17,7 @@ $mat-bottom-sheet-container-horizontal-padding: 16px !default; max-height: 80vh; overflow: auto; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: 1px solid; } } diff --git a/src/material/bottom-sheet/bottom-sheet-module.ts b/src/material/bottom-sheet/bottom-sheet-module.ts index 62c3ab860740..5ccbcf3a7495 100644 --- a/src/material/bottom-sheet/bottom-sheet-module.ts +++ b/src/material/bottom-sheet/bottom-sheet-module.ts @@ -8,7 +8,6 @@ import {OverlayModule} from '@angular/cdk/overlay'; import {PortalModule} from '@angular/cdk/portal'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; import {MatBottomSheetContainer} from './bottom-sheet-container'; @@ -16,7 +15,6 @@ import {MatBottomSheetContainer} from './bottom-sheet-container'; @NgModule({ imports: [ - CommonModule, OverlayModule, MatCommonModule, PortalModule, diff --git a/src/material/button-toggle/button-toggle.scss b/src/material/button-toggle/button-toggle.scss index 5d3f96136c71..10b28669df68 100644 --- a/src/material/button-toggle/button-toggle.scss +++ b/src/material/button-toggle/button-toggle.scss @@ -20,7 +20,7 @@ $mat-button-toggle-legacy-border-radius: 2px !default; border-radius: $mat-button-toggle-legacy-border-radius; -webkit-tap-highlight-color: transparent; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } @@ -29,7 +29,7 @@ $mat-button-toggle-legacy-border-radius: 2px !default; .mat-button-toggle-group-appearance-standard { border-radius: $mat-button-toggle-standard-border-radius; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: 0; } } @@ -58,7 +58,7 @@ $mat-button-toggle-legacy-border-radius: 2px !default; opacity: 1; // In high contrast mode `opacity: 1` will show the overlay as solid so we fall back 0.5. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; } } @@ -78,7 +78,7 @@ $mat-button-toggle-legacy-border-radius: 2px !default; &.cdk-keyboard-focused:not(.mat-button-toggle-disabled) .mat-button-toggle-focus-overlay { opacity: 0.12; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; } } @@ -128,14 +128,14 @@ $mat-button-toggle-legacy-border-radius: 2px !default; // Changing the background color for the selected item won't be visible in high contrast mode. // We fall back to using the overlay to draw a brighter, semi-transparent tint on top instead. // It uses a border, because the browser will render it using a brighter color. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; height: 0; } } } -@include cdk-high-contrast { +@include cdk-high-contrast(active, off) { .mat-button-toggle-checked { &.mat-button-toggle-appearance-standard .mat-button-toggle-focus-overlay { border-bottom: solid $mat-button-toggle-standard-height; diff --git a/src/material/button/BUILD.bazel b/src/material/button/BUILD.bazel index 1e1aac3d28e3..53842899a34e 100644 --- a/src/material/button/BUILD.bazel +++ b/src/material/button/BUILD.bazel @@ -24,7 +24,6 @@ ng_module( "//src/cdk/a11y", "//src/material/core", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/platform-browser", ], diff --git a/src/material/button/button-module.ts b/src/material/button/button-module.ts index dd40b713033a..63912366d8b2 100644 --- a/src/material/button/button-module.ts +++ b/src/material/button/button-module.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatRippleModule} from '@angular/material/core'; import {MatAnchor, MatButton} from './button'; @@ -14,7 +13,6 @@ import {MatAnchor, MatButton} from './button'; @NgModule({ imports: [ - CommonModule, MatRippleModule, MatCommonModule, ], diff --git a/src/material/button/button.scss b/src/material/button/button.scss index 284562ec06aa..102321248759 100644 --- a/src/material/button/button.scss +++ b/src/material/button/button.scss @@ -121,14 +121,14 @@ transition: none; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { // Note that IE will render this in the same way, no // matter whether the theme is light or dark. This helps // with the readability of focused buttons. background-color: #fff; } - @include cdk-high-contrast(black-on-white) { + @include cdk-high-contrast(black-on-white, off) { // For the black-on-white high contrast mode, the browser will set this element // to white, making it blend in with the background, hence why we need to set // it explicitly to black. @@ -167,7 +167,7 @@ // 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 { +@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; } diff --git a/src/material/button/button.ts b/src/material/button/button.ts index abb597ad7bfa..29e1aacaf76b 100644 --- a/src/material/button/button.ts +++ b/src/material/button/button.ts @@ -181,7 +181,4 @@ export class MatAnchor extends MatButton { event.stopImmediatePropagation(); } } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material/card/card.md b/src/material/card/card.md index 6ac3631e394b..47259a7d0c0c 100644 --- a/src/material/card/card.md +++ b/src/material/card/card.md @@ -8,14 +8,14 @@ The most basic card needs only an `` element with some content. Howeve provides a number of preset sections that you can use inside of an ``: -| Element | Description | -|-----------------------|--------------------------------------------------------------------------| -| `` | Card title | -| `` | Card subtitle | -| `` | Primary card content. Intended for blocks of text | -| `` | Card image. Stretches the image to the container width | -| `` | Container for buttons at the bottom of the card | -| `` | Section anchored to the bottom of the card | +| Element | Description | +| ---------------------- | ------------------------------------------------------ | +| `` | Card title | +| `` | Card subtitle | +| `` | Primary card content. Intended for blocks of text | +| `` | Card image. Stretches the image to the container width | +| `` | Container for buttons at the bottom of the card | +| `` | Section anchored to the bottom of the card | These elements primary serve as pre-styled content containers without any additional APIs. However, the `align` property on `` can be used to position the actions at the @@ -26,11 +26,11 @@ However, the `align` property on `` can be used to position th In addition to the aforementioned sections, `` gives the ability to add a rich header to a card. This header can contain: -| Element | Description | -|------------------------|-------------------------------------------------------------------------| -| `` | A title within the header | -| `` | A subtitle within the header | -| `` | An image used as an avatar within the header | +| Element | Description | +| ----------------------- | -------------------------------------------- | +| `` | A title within the header | +| `` | A subtitle within the header | +| `` | An image used as an avatar within the header | ### Title groups diff --git a/src/material/card/card.scss b/src/material/card/card.scss index 05509c8a136c..61e1119649c5 100644 --- a/src/material/card/card.scss +++ b/src/material/card/card.scss @@ -37,7 +37,7 @@ $mat-card-header-size: 40px !default; } } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/checkbox/BUILD.bazel b/src/material/checkbox/BUILD.bazel index dfaaac846eb3..1efa6637efd8 100644 --- a/src/material/checkbox/BUILD.bazel +++ b/src/material/checkbox/BUILD.bazel @@ -26,7 +26,6 @@ ng_module( "//src/cdk/observers", "//src/material/core", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/forms", "@npm//@angular/platform-browser", diff --git a/src/material/checkbox/_checkbox-theme.scss b/src/material/checkbox/_checkbox-theme.scss index f31b5eb1566c..16afb64a90e6 100644 --- a/src/material/checkbox/_checkbox-theme.scss +++ b/src/material/checkbox/_checkbox-theme.scss @@ -1,6 +1,5 @@ @import '../core/theming/theming'; @import '../core/typography/typography-utils'; -@import '../../cdk/a11y/a11y'; @mixin mat-checkbox-theme($theme) { @@ -34,12 +33,6 @@ // !important is needed here because a stroke must be set as an // attribute on the SVG in order for line animation to work properly. stroke: $checkbox-mark-color !important; - - @include cdk-high-contrast(black-on-white) { - // Having the one above be !important ends up overriding the browser's automatic - // color inversion so we need to re-invert it ourselves for black-on-white. - stroke: #000 !important; - } } .mat-checkbox-mixedmark { @@ -77,19 +70,6 @@ .mat-checkbox-label { color: mat-color($foreground, secondary-text); } - - @include cdk-high-contrast { - opacity: 0.5; - } - } - - // This one is moved down here so it can target both - // the theme colors and the disabled state. - @include cdk-high-contrast { - .mat-checkbox-background { - // Needs to be removed because it hides the checkbox outline. - background: none; - } } // Switch this to a solid color since we're using `opacity` diff --git a/src/material/checkbox/checkbox-module.ts b/src/material/checkbox/checkbox-module.ts index 33580b001d76..2c003fed71f7 100644 --- a/src/material/checkbox/checkbox-module.ts +++ b/src/material/checkbox/checkbox-module.ts @@ -7,7 +7,6 @@ */ import {ObserversModule} from '@angular/cdk/observers'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatRippleModule} from '@angular/material/core'; import {MatCheckbox} from './checkbox'; @@ -24,7 +23,7 @@ export class _MatCheckboxRequiredValidatorModule { @NgModule({ imports: [ - CommonModule, MatRippleModule, MatCommonModule, ObserversModule, + MatRippleModule, MatCommonModule, ObserversModule, _MatCheckboxRequiredValidatorModule ], exports: [MatCheckbox, MatCommonModule, _MatCheckboxRequiredValidatorModule], diff --git a/src/material/checkbox/checkbox.scss b/src/material/checkbox/checkbox.scss index 208e27b5dce2..44b79e9ad5ef 100644 --- a/src/material/checkbox/checkbox.scss +++ b/src/material/checkbox/checkbox.scss @@ -253,7 +253,7 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default; transition: none; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { // Note that we change the border style of the checkbox frame to dotted because this // is how IE/Edge similarly treats native checkboxes in high contrast mode. .mat-checkbox.cdk-keyboard-focused & { @@ -275,6 +275,15 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default; ._mat-animation-noopable & { transition: none; } + + // `.mat-checkbox` here is redundant, but we need it to increase the specificity so that + // these styles don't get overwritten by the `background-color` from the theme. + .mat-checkbox & { + @include cdk-high-contrast(active, off) { + // Needs to be removed because it hides the checkbox outline. + background: none; + } + } } .mat-checkbox-persistent-ripple { @@ -319,6 +328,12 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default; dasharray: $_mat-checkbox-mark-path-length; width: $_mat-checkbox-mark-stroke-size; } + + @include cdk-high-contrast(black-on-white, off) { + // In the checkbox theme this `stroke` has !important which ends up overriding the browser's + // automatic color inversion so we need to re-invert it ourselves for black-on-white. + stroke: #000 !important; + } } .mat-checkbox-mixedmark { @@ -330,7 +345,7 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default; transform: scaleX(0) rotate(0deg); border-radius: 2px; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: solid $height; margin-top: $height; @@ -393,6 +408,10 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default; .mat-checkbox-disabled { cursor: default; + + @include cdk-high-contrast(active, off) { + opacity: 0.5; + } } .mat-checkbox-anim { diff --git a/src/material/checkbox/checkbox.ts b/src/material/checkbox/checkbox.ts index 4a7095dc93fd..081ca224f278 100644 --- a/src/material/checkbox/checkbox.ts +++ b/src/material/checkbox/checkbox.ts @@ -337,7 +337,11 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc } _getAriaChecked(): 'true' | 'false' | 'mixed' { - return this.checked ? 'true' : (this.indeterminate ? 'mixed' : 'false'); + if (this.checked) { + return 'true'; + } + + return this.indeterminate ? 'mixed' : 'false'; } private _transitionCheckState(newState: TransitionCheckState) { diff --git a/src/material/chips/chip-input.spec.ts b/src/material/chips/chip-input.spec.ts index 9465b7e45b84..7a93af6bca84 100644 --- a/src/material/chips/chip-input.spec.ts +++ b/src/material/chips/chip-input.spec.ts @@ -138,6 +138,15 @@ describe('MatChipInput', () => { expect(listElement.getAttribute('tabindex')).toBe('0', 'Expected tabindex to remain 0'); })); + it('should be aria-required if the chip list is required', () => { + expect(inputNativeElement.hasAttribute('aria-required')).toBe(false); + + fixture.componentInstance.required = true; + fixture.detectChanges(); + + expect(inputNativeElement.getAttribute('aria-required')).toBe('true'); + }); + }); describe('[addOnBlur]', () => { @@ -245,7 +254,7 @@ describe('MatChipInput', () => { @Component({ template: ` - + Hello { }) class TestChipInput { @ViewChild(MatChipList) chipListInstance: MatChipList; - addOnBlur: boolean = false; + addOnBlur = false; + required = false; placeholder = ''; add(_: MatChipInputEvent) { diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index f57c957994f1..be5253b4ae55 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -43,6 +43,7 @@ let nextUniqueId = 0; '[attr.disabled]': 'disabled || null', '[attr.placeholder]': 'placeholder || null', '[attr.aria-invalid]': '_chipList && _chipList.ngControl ? _chipList.ngControl.invalid : null', + '[attr.aria-required]': '_chipList && _chipList.required || null', } }) export class MatChipInput implements MatChipTextControl, OnChanges { diff --git a/src/material/chips/chip-remove.spec.ts b/src/material/chips/chip-remove.spec.ts index ad489ab0556c..08874145bedf 100644 --- a/src/material/chips/chip-remove.spec.ts +++ b/src/material/chips/chip-remove.spec.ts @@ -30,12 +30,18 @@ describe('Chip Remove', () => { })); describe('basic behavior', () => { - it('should applies the `mat-chip-remove` CSS class', () => { + it('should apply the `mat-chip-remove` CSS class', () => { let buttonElement = chipNativeElement.querySelector('button')!; expect(buttonElement.classList).toContain('mat-chip-remove'); }); + it('should ensure that the button cannot submit its parent form', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + expect(buttonElement.getAttribute('type')).toBe('button'); + }); + it('should emits (removed) on click', () => { let buttonElement = chipNativeElement.querySelector('button')!; diff --git a/src/material/chips/chip.spec.ts b/src/material/chips/chip.spec.ts index bb1ab9368b51..d89cdeda9c73 100644 --- a/src/material/chips/chip.spec.ts +++ b/src/material/chips/chip.spec.ts @@ -15,14 +15,18 @@ describe('MatChip', () => { let chipNativeElement: HTMLElement; let chipInstance: MatChip; let globalRippleOptions: RippleGlobalOptions; - let dir = 'ltr'; beforeEach(async(() => { globalRippleOptions = {}; TestBed.configureTestingModule({ imports: [MatChipsModule], - declarations: [BasicChip, SingleChip], + declarations: [ + BasicChip, + SingleChip, + BasicChipWithStaticTabindex, + BasicChipWithBoundTabindex, + ], providers: [ {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions}, {provide: Directionality, useFactory: () => ({ @@ -36,22 +40,38 @@ describe('MatChip', () => { })); describe('MatBasicChip', () => { - - beforeEach(() => { + it('adds the `mat-basic-chip` class', () => { fixture = TestBed.createComponent(BasicChip); fixture.detectChanges(); - chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!; - chipNativeElement = chipDebugElement.nativeElement; - chipInstance = chipDebugElement.injector.get(MatChip); + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.classList).toContain('mat-chip'); + expect(chip.classList).toContain('mat-basic-chip'); }); - it('adds the `mat-basic-chip` class', () => { - expect(chipNativeElement.classList).toContain('mat-chip'); - expect(chipNativeElement.classList).toContain('mat-basic-chip'); + it('should be able to set a static tabindex', () => { + fixture = TestBed.createComponent(BasicChipWithStaticTabindex); + fixture.detectChanges(); + + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.getAttribute('tabindex')).toBe('3'); + }); + + it('should be able to set a dynamic tabindex', () => { + fixture = TestBed.createComponent(BasicChipWithBoundTabindex); + fixture.detectChanges(); + + const chip = fixture.nativeElement.querySelector('mat-basic-chip'); + expect(chip.getAttribute('tabindex')).toBe('12'); + + fixture.componentInstance.tabindex = 15; + fixture.detectChanges(); + + expect(chip.getAttribute('tabindex')).toBe('15'); }); }); + describe('MatChip', () => { let testComponent: SingleChip; @@ -426,7 +446,20 @@ class SingleChip { } @Component({ - template: `{{name}}` + template: `Hello` }) class BasicChip { } + +@Component({ + template: `Hello` +}) +class BasicChipWithStaticTabindex { +} + +@Component({ + template: `Hello` +}) +class BasicChipWithBoundTabindex { + tabindex = 12; +} diff --git a/src/material/chips/chip.ts b/src/material/chips/chip.ts index 28fb69121a0c..c55e267752e2 100644 --- a/src/material/chips/chip.ts +++ b/src/material/chips/chip.ts @@ -23,6 +23,7 @@ import { Optional, Output, ChangeDetectorRef, + Attribute, } from '@angular/core'; import { CanColor, @@ -31,6 +32,9 @@ import { CanDisableCtor, CanDisableRipple, CanDisableRippleCtor, + HasTabIndex, + HasTabIndexCtor, + mixinTabIndex, MAT_RIPPLE_GLOBAL_OPTIONS, mixinColor, mixinDisabled, @@ -69,8 +73,9 @@ class MatChipBase { constructor(public _elementRef: ElementRef) {} } -const _MatChipMixinBase: CanColorCtor & CanDisableRippleCtor & CanDisableCtor & typeof MatChipBase = - mixinColor(mixinDisableRipple(mixinDisabled(MatChipBase)), 'primary'); +const _MatChipMixinBase: CanColorCtor & CanDisableRippleCtor & CanDisableCtor & + HasTabIndexCtor & typeof MatChipBase = + mixinTabIndex(mixinColor(mixinDisableRipple(mixinDisabled(MatChipBase)), 'primary'), -1); /** * Dummy directive to add CSS class to chip avatar. @@ -97,11 +102,11 @@ export class MatChipTrailingIcon {} */ @Directive({ selector: `mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]`, - inputs: ['color', 'disabled', 'disableRipple'], + inputs: ['color', 'disabled', 'disableRipple', 'tabIndex'], exportAs: 'matChip', host: { 'class': 'mat-chip', - '[attr.tabindex]': 'disabled ? null : -1', + '[attr.tabindex]': 'disabled ? null : tabIndex', 'role': 'option', '[class.mat-chip-selected]': 'selected', '[class.mat-chip-with-avatar]': 'avatar', @@ -118,7 +123,7 @@ export class MatChipTrailingIcon {} }, }) export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDestroy, CanColor, - CanDisable, CanDisableRipple, RippleTarget { + CanDisable, CanDisableRipple, RippleTarget, HasTabIndex { /** Reference to the RippleRenderer for the chip. */ private _chipRipple: RippleRenderer; @@ -238,7 +243,8 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes // @breaking-change 8.0.0 `animationMode` parameter to become required. @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string, // @breaking-change 9.0.0 `_changeDetectorRef` parameter to become required. - private _changeDetectorRef?: ChangeDetectorRef) { + private _changeDetectorRef?: ChangeDetectorRef, + @Attribute('tabindex') tabIndex?: string) { super(_elementRef); this._addHostClassName(); @@ -247,6 +253,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes this._chipRipple.setupTriggerEvents(_elementRef); this.rippleConfig = globalRippleOptions || {}; this._animationsDisabled = animationMode === 'NoopAnimations'; + this.tabIndex = tabIndex != null ? (parseInt(tabIndex) || -1) : -1; } _addHostClassName() { @@ -415,6 +422,9 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes host: { 'class': 'mat-chip-remove mat-chip-trailing-icon', '(click)': '_handleClick($event)', + + // Prevent accidental form submissions. + 'type': 'button', } }) export class MatChipRemove { diff --git a/src/material/chips/chips.scss b/src/material/chips/chips.scss index fc4ebaa0d252..2602de84a268 100644 --- a/src/material/chips/chips.scss +++ b/src/material/chips/chips.scss @@ -82,7 +82,7 @@ $mat-chip-remove-size: 18px; } } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; &:focus { diff --git a/src/material/config.bzl b/src/material/config.bzl index 13cf4c5d8451..ed921eab309c 100644 --- a/src/material/config.bzl +++ b/src/material/config.bzl @@ -24,7 +24,9 @@ entryPoints = [ "expansion/testing", "form-field", "grid-list", + "grid-list/testing", "icon", + "icon/testing", "input", "list", "list/testing", diff --git a/src/material/core/label/label-options.ts b/src/material/core/label/label-options.ts index 67ea0d79f765..a341799b236a 100644 --- a/src/material/core/label/label-options.ts +++ b/src/material/core/label/label-options.ts @@ -8,14 +8,27 @@ import {InjectionToken} from '@angular/core'; -/** InjectionToken that can be used to specify the global label options. */ +/** + * InjectionToken that can be used to specify the global label options. + * @deprecated Use `MAT_FORM_FIELD_DEFAULT_OPTIONS` injection token from + * `@angular/material/form-field` instead. + * @breaking-change 11.0.0 + */ export const MAT_LABEL_GLOBAL_OPTIONS = new InjectionToken('mat-label-global-options'); -/** Type for the available floatLabel values. */ +/** + * Type for the available floatLabel values. + * @deprecated Use `FloatLabelType` from `@angular/material/form-field` instead. + * @breaking-change 11.0.0 + */ export type FloatLabelType = 'always' | 'never' | 'auto'; -/** Configurable options for floating labels. */ +/** + * Configurable options for floating labels. + * @deprecated Use `MatFormFieldDefaultOptions` from `@angular/material/form-field` instead. + * @breaking-change 11.0.0 + */ export interface LabelOptions { /** * Whether the label should float `always`, `never`, or `auto` (only when necessary). diff --git a/src/material/core/option/option.scss b/src/material/core/option/option.scss index d1d03547dc37..6a6d3e5fb771 100644 --- a/src/material/core/option/option.scss +++ b/src/material/core/option/option.scss @@ -29,7 +29,7 @@ } } - @include cdk-high-contrast { + @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 @@ -69,7 +69,7 @@ pointer-events: none; // Prevents the ripple from completely covering the option in high contrast mode. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; } } diff --git a/src/material/core/ripple/_ripple.scss b/src/material/core/ripple/_ripple.scss index 3f94c6109f44..f8a1c1c8573a 100644 --- a/src/material/core/ripple/_ripple.scss +++ b/src/material/core/ripple/_ripple.scss @@ -13,6 +13,15 @@ $mat-ripple-color-opacity: 0.1; // By default, every ripple container should have position: relative in favor of creating an // easy API for developers using the MatRipple directive. position: relative; + + // Promote containers that have ripples to a new layer. We want to target `:not(:empty)`, + // because we don't want all ripple containers to have their own layer since they're used in a + // lot of places and the layer is only relevant while animating. Note that ideally we'd use + // the `contain` property here (see #13175), because `:empty` can be broken by having extra + // text inside the element, but it isn't very well supported yet. + &:not(:empty) { + transform: translateZ(0); + } } .mat-ripple.mat-ripple-unbounded { @@ -28,7 +37,7 @@ $mat-ripple-color-opacity: 0.1; transform: scale(0); // In high contrast mode the ripple is opaque, causing it to obstruct the content. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { display: none; } } diff --git a/src/material/core/ripple/ripple-renderer.ts b/src/material/core/ripple/ripple-renderer.ts index ff07037ec821..c3e2629b3cae 100644 --- a/src/material/core/ripple/ripple-renderer.ts +++ b/src/material/core/ripple/ripple-renderer.ts @@ -146,9 +146,12 @@ export class RippleRenderer { ripple.style.height = `${radius * 2}px`; ripple.style.width = `${radius * 2}px`; - // If the color is not set, the default CSS color will be used. - // TODO(TS3.7): Type 'string | null' is not assignable to type 'string'. - ripple.style.backgroundColor = (config.color || null) as any; + // If a custom color has been specified, set it as inline style. If no color is + // set, the default color will be applied through the ripple theme styles. + if (config.color != null) { + ripple.style.backgroundColor = config.color; + } + ripple.style.transitionDuration = `${duration}ms`; this._containerElement.appendChild(ripple); diff --git a/src/material/core/ripple/ripple.spec.ts b/src/material/core/ripple/ripple.spec.ts index 6383c3eff773..307ef4c1a90b 100644 --- a/src/material/core/ripple/ripple.spec.ts +++ b/src/material/core/ripple/ripple.spec.ts @@ -49,7 +49,7 @@ describe('MatRipple', () => { })); afterEach(() => { - document.body.style.margin = originalBodyMargin; + document.body.style.margin = originalBodyMargin!; }); describe('basic ripple', () => { diff --git a/src/material/datepicker/calendar-body.scss b/src/material/datepicker/calendar-body.scss index 2002566454ab..53e2c24382e8 100644 --- a/src/material/datepicker/calendar-body.scss +++ b/src/material/datepicker/calendar-body.scss @@ -61,13 +61,13 @@ $mat-calendar-body-cell-content-size: 100% - $mat-calendar-body-cell-content-mar // Choosing a value clearly larger than the height ensures we get the correct capsule shape. border-radius: 999px; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border: none; } } -@include cdk-high-contrast { +@include cdk-high-contrast(active, off) { .mat-datepicker-popup:not(:empty), .mat-calendar-body-selected { outline: solid 1px; diff --git a/src/material/datepicker/calendar.spec.ts b/src/material/datepicker/calendar.spec.ts index 2bbfb3f747c2..980550d08918 100644 --- a/src/material/datepicker/calendar.spec.ts +++ b/src/material/datepicker/calendar.spec.ts @@ -283,6 +283,45 @@ describe('MatCalendar', () => { }); }); + it('should re-render the month view when the locale changes', + inject([DateAdapter], (adapter: DateAdapter) => { + fixture.detectChanges(); + spyOn(calendarInstance.monthView, '_init').and.callThrough(); + + adapter.setLocale('bg-BG'); + fixture.detectChanges(); + + expect(calendarInstance.monthView._init).toHaveBeenCalled(); + })); + + it('should re-render the year view when the locale changes', + inject([DateAdapter], (adapter: DateAdapter) => { + periodButton.click(); + fixture.detectChanges(); + + (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + fixture.detectChanges(); + + spyOn(calendarInstance.yearView, '_init').and.callThrough(); + + adapter.setLocale('bg-BG'); + fixture.detectChanges(); + + expect(calendarInstance.yearView._init).toHaveBeenCalled(); + })); + + it('should re-render the multi-year view when the locale changes', + inject([DateAdapter], (adapter: DateAdapter) => { + periodButton.click(); + fixture.detectChanges(); + + spyOn(calendarInstance.multiYearView, '_init').and.callThrough(); + + adapter.setLocale('bg-BG'); + fixture.detectChanges(); + + expect(calendarInstance.multiYearView._init).toHaveBeenCalled(); + })); }); describe('calendar with min and max date', () => { diff --git a/src/material/datepicker/calendar.ts b/src/material/datepicker/calendar.ts index 9b911d58edcf..bc008d1f589a 100644 --- a/src/material/datepicker/calendar.ts +++ b/src/material/datepicker/calendar.ts @@ -353,8 +353,16 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes /** Updates today's date after an update of the active date */ updateTodaysDate() { - let view = this.currentView == 'month' ? this.monthView : - (this.currentView == 'year' ? this.yearView : this.multiYearView); + const currentView = this.currentView; + let view: MatMonthView | MatYearView | MatMultiYearView; + + if (currentView === 'month') { + view = this.monthView; + } else if (currentView === 'year') { + view = this.yearView; + } else { + view = this.multiYearView; + } view.ngAfterContentInit(); } diff --git a/src/material/datepicker/datepicker-content.scss b/src/material/datepicker/datepicker-content.scss index 6a5326adb6e4..d8a55bf88834 100644 --- a/src/material/datepicker/datepicker-content.scss +++ b/src/material/datepicker/datepicker-content.scss @@ -33,12 +33,12 @@ $mat-datepicker-touch-max-height: 788px; .mat-datepicker-content-touch { display: block; - // make sure the dialog scrolls rather than being cropped on ludicrously small screens + // Make sure the dialog scrolls rather than being cropped on ludicrously small screens max-height: 80vh; overflow: auto; - // TODO(mmalerba): hack to offset the padding of the dialog. Can be removed when we switch away - // from using dialog. + // Offsets the padding of the dialog. + // TODO(mmalerba): Remove when we switch away from using dialog. margin: -24px; .mat-calendar { diff --git a/src/material/datepicker/datepicker-input.ts b/src/material/datepicker/datepicker-input.ts index bb74cceab489..29bd5b71a1c8 100644 --- a/src/material/datepicker/datepicker-input.ts +++ b/src/material/datepicker/datepicker-input.ts @@ -320,6 +320,7 @@ export class MatDatepickerInput implements ControlValueAccessor, OnDestroy, V } _onInput(value: string) { + const lastValueWasValid = this._lastValueValid; let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput); this._lastValueValid = !date || this._dateAdapter.isValid(date); date = this._getValidDateOrNull(date); @@ -329,7 +330,7 @@ export class MatDatepickerInput implements ControlValueAccessor, OnDestroy, V this._cvaOnChange(date); this._valueChange.emit(date); this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); - } else { + } else if (lastValueWasValid !== this._lastValueValid) { this._validatorOnChange(); } } diff --git a/src/material/datepicker/datepicker-toggle.ts b/src/material/datepicker/datepicker-toggle.ts index 71e35d0a0664..69a85b2c9cb2 100644 --- a/src/material/datepicker/datepicker-toggle.ts +++ b/src/material/datepicker/datepicker-toggle.ts @@ -43,7 +43,7 @@ export class MatDatepickerToggleIcon {} 'class': 'mat-datepicker-toggle', // Always set the tabindex to -1 so that it doesn't overlap with any custom tabindex the // consumer may have provided, while still being able to receive focus. - '[attr.tabindex]': '-1', + '[attr.tabindex]': 'disabled ? null : -1', '[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened', '[class.mat-accent]': 'datepicker && datepicker.color === "accent"', '[class.mat-warn]': 'datepicker && datepicker.color === "warn"', diff --git a/src/material/datepicker/datepicker.md b/src/material/datepicker/datepicker.md index f3871c3460c3..6bef68c17955 100644 --- a/src/material/datepicker/datepicker.md +++ b/src/material/datepicker/datepicker.md @@ -210,20 +210,62 @@ It's also possible to set the locale at runtime using the `setLocale` method of The datepicker was built to be date implementation agnostic. This means that it can be made to work with a variety of different date implementations. However it also means that developers need to make sure to provide the appropriate pieces for the datepicker to work with their chosen implementation. -The easiest way to ensure this is just to import one of the pre-made modules: -|Module |Date type|Supported locales |Dependencies |Import from | -|---------------------|---------|-----------------------------------------------------------------------|----------------------------------|----------------------------------| -|`MatNativeDateModule`|`Date` |en-US |None |`@angular/material` | -|`MatMomentDateModule`|`Moment` |[See project](https://github.com/moment/moment/tree/develop/src/locale)|[Moment.js](https://momentjs.com/)|`@angular/material-moment-adapter`| - -*Please note: `MatNativeDateModule` is based off of the functionality available in JavaScript's -native `Date` object, and is thus not suitable for many locales. One of the biggest shortcomings of -the native `Date` object is the inability to set the parse format. We highly recommend using the -`MomentDateAdapter` or a custom `DateAdapter` that works with the formatting/parsing library of your -choice.* - -These modules include providers for `DateAdapter` and `MAT_DATE_FORMATS` +The easiest way to ensure this is to import one of the provided date modules: + +`MatNativeDateModule` + + + + + + + + + + + + + + + + + + + + +
    Date typeDate
    Supported localesen-US
    DependenciesNone
    Import from@angular/material/core
    + +`MatMomentDateModule` + + + + + + + + + + + + + + + + + + + + +
    Date typeMoment
    Supported localesSee project
    DependenciesMoment.js
    Import from@angular/material-moment-adapter
    + +*Please note: `MatNativeDateModule` is based off the functionality available in JavaScript's +native [`Date` object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date). +Thus it is not suitable for many locales. One of the biggest shortcomings of the native `Date` +object is the inability to set the parse format. We highly recommend using the `MomentDateAdapter` +or a custom `DateAdapter` that works with the formatting/parsing library of your choice.* + +These modules include providers for `DateAdapter` and `MAT_DATE_FORMATS`. ```ts @NgModule({ @@ -292,7 +334,7 @@ export class MyApp {} #### Customizing the parse and display formats -The `MAT_DATE_FORMATS` object is just a collection of formats that the datepicker uses when parsing +The `MAT_DATE_FORMATS` object is a collection of formats that the datepicker uses when parsing and displaying dates. These formats are passed through to the `DateAdapter` so you will want to make sure that the format objects you're using are compatible with the `DateAdapter` used in your app. diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index 21f237f62b4d..e2ec693f1fab 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -876,6 +876,44 @@ describe('MatDatepicker', () => { expect(testComponent.datepickerToggle.disabled).toBe(true); }); + + it('should not dispatch FormControl change event for invalid values on input when set ' + + 'to update on blur', fakeAsync(() => { + const formControl = new FormControl({value: null}, {updateOn: 'blur'}); + const spy = jasmine.createSpy('change spy'); + const subscription = formControl.valueChanges.subscribe(spy); + const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; + const setValue = (value: string) => { + inputEl.value = value; + dispatchFakeEvent(inputEl, 'input'); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + }; + + fixture.componentInstance.formControl = formControl; + fixture.detectChanges(); + + expect(spy).not.toHaveBeenCalled(); + + setValue('10/10/2010'); + expect(spy).not.toHaveBeenCalled(); + + setValue('10/10/'); + expect(spy).not.toHaveBeenCalled(); + + setValue('10/10'); + expect(spy).not.toHaveBeenCalled(); + + dispatchFakeEvent(inputEl, 'blur'); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledTimes(1); + subscription.unsubscribe(); + })); + }); describe('datepicker with mat-datepicker-toggle', () => { @@ -1030,7 +1068,7 @@ describe('MatDatepicker', () => { expect(button.getAttribute('tabindex')).toBe('7'); }); - it('should clear the tabindex from the mat-datepicker-toggle host', () => { + it('should reset the tabindex from the mat-datepicker-toggle host', () => { const fixture = createComponent(DatepickerWithTabindexOnToggle, [MatNativeDateModule]); fixture.detectChanges(); @@ -1053,6 +1091,16 @@ describe('MatDatepicker', () => { expect(document.activeElement).toBe(button); }); + it('should remove the tabindex from the mat-datepicker-toggle host when disabled', () => { + const fixture = createComponent(DatepickerWithTabindexOnToggle, [MatNativeDateModule]); + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + const host = fixture.nativeElement.querySelector('.mat-datepicker-toggle'); + + expect(host.hasAttribute('tabindex')).toBe(false); + }); + }); describe('datepicker inside mat-form-field', () => { @@ -2040,13 +2088,15 @@ class DelayedDatepicker { @Component({ template: ` - +
    `, }) -class DatepickerWithTabindexOnToggle {} +class DatepickerWithTabindexOnToggle { + disabled = false; +} @Component({ diff --git a/src/material/datepicker/datepicker.ts b/src/material/datepicker/datepicker.ts index 635e80d866f1..f9aacd5817d9 100644 --- a/src/material/datepicker/datepicker.ts +++ b/src/material/datepicker/datepicker.ts @@ -35,6 +35,7 @@ import { ViewChild, ViewContainerRef, ViewEncapsulation, + ChangeDetectorRef, } from '@angular/core'; import { CanColor, @@ -92,7 +93,8 @@ const _MatDatepickerContentMixinBase: CanColorCtor & typeof MatDatepickerContent styleUrls: ['datepicker-content.css'], host: { 'class': 'mat-datepicker-content', - '[@transformPanel]': '"enter"', + '[@transformPanel]': '_animationState', + '(@transformPanel.done)': '_animationDone.next()', '[class.mat-datepicker-content-touch]': 'datepicker.touchUi', }, animations: [ @@ -105,7 +107,7 @@ const _MatDatepickerContentMixinBase: CanColorCtor & typeof MatDatepickerContent inputs: ['color'], }) export class MatDatepickerContent extends _MatDatepickerContentMixinBase - implements AfterViewInit, CanColor { + implements AfterViewInit, OnDestroy, CanColor { /** Reference to the internal calendar component. */ @ViewChild(MatCalendar) _calendar: MatCalendar; @@ -116,13 +118,38 @@ export class MatDatepickerContent extends _MatDatepickerContentMixinBase /** Whether the datepicker is above or below the input. */ _isAbove: boolean; - constructor(elementRef: ElementRef) { + /** Current state of the animation. */ + _animationState: 'enter' | 'void' = 'enter'; + + /** Emits when an animation has finished. */ + _animationDone = new Subject(); + + constructor( + elementRef: ElementRef, + /** + * @deprecated `_changeDetectorRef` parameter to become required. + * @breaking-change 11.0.0 + */ + private _changeDetectorRef?: ChangeDetectorRef) { super(elementRef); } ngAfterViewInit() { this._calendar.focusActiveCell(); } + + ngOnDestroy() { + this._animationDone.complete(); + } + + _startExitAnimation() { + this._animationState = 'void'; + + // @breaking-change 11.0.0 Remove null check for `_changeDetectorRef`. + if (this._changeDetectorRef) { + this._changeDetectorRef.markForCheck(); + } + } } @@ -258,14 +285,11 @@ export class MatDatepicker implements OnDestroy, CanColor { } /** A reference to the overlay when the calendar is opened as a popup. */ - _popupRef: OverlayRef; + private _popupRef: OverlayRef | null; /** A reference to the dialog when the calendar is opened as a dialog. */ private _dialogRef: MatDialogRef> | null; - /** A portal containing the calendar for this datepicker. */ - private _calendarPortal: ComponentPortal>; - /** Reference to the component instantiated in popup mode. */ private _popupComponentRef: ComponentRef> | null; @@ -300,14 +324,10 @@ export class MatDatepicker implements OnDestroy, CanColor { } ngOnDestroy() { + this._destroyPopup(); this.close(); this._inputSubscription.unsubscribe(); this._disabledChange.complete(); - - if (this._popupRef) { - this._popupRef.dispose(); - this._popupComponentRef = null; - } } /** Selects the given date */ @@ -364,16 +384,15 @@ export class MatDatepicker implements OnDestroy, CanColor { if (!this._opened) { return; } - if (this._popupRef && this._popupRef.hasAttached()) { - this._popupRef.detach(); + if (this._popupComponentRef && this._popupRef) { + const instance = this._popupComponentRef.instance; + instance._startExitAnimation(); + instance._animationDone.pipe(take(1)).subscribe(() => this._destroyPopup()); } if (this._dialogRef) { this._dialogRef.close(); this._dialogRef = null; } - if (this._calendarPortal && this._calendarPortal.isAttached) { - this._calendarPortal.detach(); - } const completeClose = () => { // The `_opened` could've been reset already if @@ -417,30 +436,24 @@ export class MatDatepicker implements OnDestroy, CanColor { this._dialogRef.afterClosed().subscribe(() => this.close()); this._dialogRef.componentInstance.datepicker = this; - this._setColor(); + this._dialogRef.componentInstance.color = this.color; } /** Open the calendar as a popup. */ private _openAsPopup(): void { - if (!this._calendarPortal) { - this._calendarPortal = new ComponentPortal>(MatDatepickerContent, - this._viewContainerRef); - } - - if (!this._popupRef) { - this._createPopup(); - } - - if (!this._popupRef.hasAttached()) { - this._popupComponentRef = this._popupRef.attach(this._calendarPortal); - this._popupComponentRef.instance.datepicker = this; - this._setColor(); - - // Update the position once the calendar has rendered. - this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => { - this._popupRef.updatePosition(); - }); - } + const portal = new ComponentPortal>(MatDatepickerContent, + this._viewContainerRef); + + this._destroyPopup(); + this._createPopup(); + const ref = this._popupComponentRef = this._popupRef!.attach(portal); + ref.instance.datepicker = this; + ref.instance.color = this.color; + + // Update the position once the calendar has rendered. + this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => { + this._popupRef!.updatePosition(); + }); } /** Create the popup. */ @@ -474,6 +487,14 @@ export class MatDatepicker implements OnDestroy, CanColor { }); } + /** Destroys the current popup overlay. */ + private _destroyPopup() { + if (this._popupRef) { + this._popupRef.dispose(); + this._popupRef = this._popupComponentRef = null; + } + } + /** Create the popup PositionStrategy. */ private _createPopupPositionStrategy(): PositionStrategy { return this._overlay.position() @@ -518,17 +539,6 @@ export class MatDatepicker implements OnDestroy, CanColor { return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null; } - /** Passes the current theme color along to the calendar overlay. */ - private _setColor(): void { - const color = this.color; - if (this._popupComponentRef) { - this._popupComponentRef.instance.color = color; - } - if (this._dialogRef) { - this._dialogRef.componentInstance.color = color; - } - } - static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_touchUi: BooleanInput; } diff --git a/src/material/datepicker/month-view.ts b/src/material/datepicker/month-view.ts index fc5836bd9a71..3d414fe08eac 100644 --- a/src/material/datepicker/month-view.ts +++ b/src/material/datepicker/month-view.ts @@ -30,11 +30,14 @@ import { Output, ViewEncapsulation, ViewChild, + OnDestroy, } from '@angular/core'; import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; import {Directionality} from '@angular/cdk/bidi'; import {MatCalendarBody, MatCalendarCell, MatCalendarCellCssClasses} from './calendar-body'; import {createMissingDateImplError} from './datepicker-errors'; +import {Subscription} from 'rxjs'; +import {startWith} from 'rxjs/operators'; const DAYS_PER_WEEK = 7; @@ -51,7 +54,9 @@ const DAYS_PER_WEEK = 7; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class MatMonthView implements AfterContentInit { +export class MatMonthView implements AfterContentInit, OnDestroy { + private _rerenderSubscription = Subscription.EMPTY; + /** * The date to display in this month view (everything other than the month and year is ignored). */ @@ -147,7 +152,13 @@ export class MatMonthView implements AfterContentInit { } ngAfterContentInit() { - this._init(); + this._rerenderSubscription = this._dateAdapter.localeChanges + .pipe(startWith(null)) + .subscribe(() => this._init()); + } + + ngOnDestroy() { + this._rerenderSubscription.unsubscribe(); } /** Handles when a new date is selected. */ diff --git a/src/material/datepicker/multi-year-view.ts b/src/material/datepicker/multi-year-view.ts index fa3a8886529f..328cdf6445da 100644 --- a/src/material/datepicker/multi-year-view.ts +++ b/src/material/datepicker/multi-year-view.ts @@ -29,11 +29,14 @@ import { Output, ViewChild, ViewEncapsulation, + OnDestroy, } from '@angular/core'; import {DateAdapter} from '@angular/material/core'; import {Directionality} from '@angular/cdk/bidi'; import {MatCalendarBody, MatCalendarCell} from './calendar-body'; import {createMissingDateImplError} from './datepicker-errors'; +import {Subscription} from 'rxjs'; +import {startWith} from 'rxjs/operators'; export const yearsPerPage = 24; @@ -50,7 +53,9 @@ export const yearsPerRow = 4; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class MatMultiYearView implements AfterContentInit { +export class MatMultiYearView implements AfterContentInit, OnDestroy { + private _rerenderSubscription = Subscription.EMPTY; + /** The date to display in this multi-year view (everything other than the year is ignored). */ @Input() get activeDate(): D { return this._activeDate; } @@ -127,7 +132,13 @@ export class MatMultiYearView implements AfterContentInit { } ngAfterContentInit() { - this._init(); + this._rerenderSubscription = this._dateAdapter.localeChanges + .pipe(startWith(null)) + .subscribe(() => this._init()); + } + + ngOnDestroy() { + this._rerenderSubscription.unsubscribe(); } /** Initializes this multi-year view. */ diff --git a/src/material/datepicker/year-view.ts b/src/material/datepicker/year-view.ts index 5d2ffe15bc8c..769eb328bf01 100644 --- a/src/material/datepicker/year-view.ts +++ b/src/material/datepicker/year-view.ts @@ -30,11 +30,14 @@ import { Output, ViewChild, ViewEncapsulation, + OnDestroy, } from '@angular/core'; import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; import {Directionality} from '@angular/cdk/bidi'; import {MatCalendarBody, MatCalendarCell} from './calendar-body'; import {createMissingDateImplError} from './datepicker-errors'; +import {Subscription} from 'rxjs'; +import {startWith} from 'rxjs/operators'; /** * An internal component used to display a single year in the datepicker. @@ -47,7 +50,9 @@ import {createMissingDateImplError} from './datepicker-errors'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class MatYearView implements AfterContentInit { +export class MatYearView implements AfterContentInit, OnDestroy { + private _rerenderSubscription = Subscription.EMPTY; + /** The date to display in this year view (everything other than the year is ignored). */ @Input() get activeDate(): D { return this._activeDate; } @@ -132,7 +137,13 @@ export class MatYearView implements AfterContentInit { } ngAfterContentInit() { - this._init(); + this._rerenderSubscription = this._dateAdapter.localeChanges + .pipe(startWith(null)) + .subscribe(() => this._init()); + } + + ngOnDestroy() { + this._rerenderSubscription.unsubscribe(); } /** Handles when a new month is selected. */ diff --git a/src/material/dialog/BUILD.bazel b/src/material/dialog/BUILD.bazel index 217cbb30b4e6..74308568754e 100644 --- a/src/material/dialog/BUILD.bazel +++ b/src/material/dialog/BUILD.bazel @@ -28,7 +28,6 @@ ng_module( "//src/cdk/portal", "//src/material/core", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/material/dialog/dialog-module.ts b/src/material/dialog/dialog-module.ts index f9ca438de105..6f80a84f3759 100644 --- a/src/material/dialog/dialog-module.ts +++ b/src/material/dialog/dialog-module.ts @@ -8,7 +8,6 @@ import {OverlayModule} from '@angular/cdk/overlay'; import {PortalModule} from '@angular/cdk/portal'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; import {MAT_DIALOG_SCROLL_STRATEGY_PROVIDER, MatDialog} from './dialog'; @@ -23,7 +22,6 @@ import { @NgModule({ imports: [ - CommonModule, OverlayModule, PortalModule, MatCommonModule, diff --git a/src/material/dialog/dialog.scss b/src/material/dialog/dialog.scss index b09d10254f68..6686504d1c80 100644 --- a/src/material/dialog/dialog.scss +++ b/src/material/dialog/dialog.scss @@ -24,7 +24,7 @@ $mat-dialog-button-margin: 8px !default; min-height: inherit; max-height: inherit; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/divider/BUILD.bazel b/src/material/divider/BUILD.bazel index 56ab1275bad0..f6308eb6c4cc 100644 --- a/src/material/divider/BUILD.bazel +++ b/src/material/divider/BUILD.bazel @@ -21,7 +21,6 @@ ng_module( deps = [ "//src/cdk/coercion", "//src/material/core", - "@npm//@angular/common", "@npm//@angular/core", ], ) diff --git a/src/material/divider/divider-module.ts b/src/material/divider/divider-module.ts index 6608b8e9cee0..66b879126262 100644 --- a/src/material/divider/divider-module.ts +++ b/src/material/divider/divider-module.ts @@ -6,14 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; import {MatDivider} from './divider'; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule], exports: [MatDivider, MatCommonModule], declarations: [MatDivider], }) diff --git a/src/material/expansion/accordion.ts b/src/material/expansion/accordion.ts index 6be761be97cd..6714f68c3bea 100644 --- a/src/material/expansion/accordion.ts +++ b/src/material/expansion/accordion.ts @@ -103,5 +103,4 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte } static ngAcceptInputType_hideToggle: BooleanInput; - static ngAcceptInputType_multi: BooleanInput; } diff --git a/src/material/expansion/expansion-panel-header.ts b/src/material/expansion/expansion-panel-header.ts index 30c4e3e61857..d70c27ccc8f9 100644 --- a/src/material/expansion/expansion-panel-header.ts +++ b/src/material/expansion/expansion-panel-header.ts @@ -148,7 +148,9 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption { /** Toggles the expanded state of the panel. */ _toggle(): void { - this.panel.toggle(); + if (!this.disabled) { + this.panel.toggle(); + } } /** Gets whether the panel is expanded. */ diff --git a/src/material/expansion/expansion-panel.scss b/src/material/expansion/expansion-panel.scss index 2ea2a908ea27..45b5866912ff 100644 --- a/src/material/expansion/expansion-panel.scss +++ b/src/material/expansion/expansion-panel.scss @@ -29,7 +29,7 @@ } } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index 7312e34d8a38..948db239293e 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -192,6 +192,21 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI return this.expanded ? 'expanded' : 'collapsed'; } + /** Toggles the expanded state of the expansion panel. */ + toggle(): void { + this.expanded = !this.expanded; + } + + /** Sets the expanded state of the expansion panel to false. */ + close(): void { + this.expanded = false; + } + + /** Sets the expanded state of the expansion panel to true. */ + open(): void { + this.expanded = true; + } + ngAfterContentInit() { if (this._lazyContent) { // Render the content as soon as the panel becomes open. diff --git a/src/material/expansion/expansion.spec.ts b/src/material/expansion/expansion.spec.ts index 0d1e2b5300e4..711c1e048a62 100644 --- a/src/material/expansion/expansion.spec.ts +++ b/src/material/expansion/expansion.spec.ts @@ -403,6 +403,52 @@ describe('MatExpansionPanel', () => { expect(header.classList).toContain('mat-expanded'); }); + it('should be able to toggle a disabled expansion panel programmatically via the ' + + 'open/close methods', () => { + const panelInstance = fixture.componentInstance.panel; + + expect(panelInstance.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + panelInstance.open(); + fixture.detectChanges(); + + expect(panelInstance.expanded).toBe(true); + expect(header.classList).toContain('mat-expanded'); + + panelInstance.close(); + fixture.detectChanges(); + + expect(panelInstance.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + }); + + it('should be able to toggle a disabled expansion panel programmatically via the ' + + 'toggle method', () => { + const panelInstance = fixture.componentInstance.panel; + + expect(panelInstance.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + panelInstance.toggle(); + fixture.detectChanges(); + + expect(panelInstance.expanded).toBe(true); + expect(header.classList).toContain('mat-expanded'); + + panelInstance.toggle(); + fixture.detectChanges(); + + expect(panelInstance.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + }); + }); }); diff --git a/src/material/form-field/form-field-fill.scss b/src/material/form-field/form-field-fill.scss index 98cc747314e6..b7366392f546 100644 --- a/src/material/form-field/form-field-fill.scss +++ b/src/material/form-field/form-field-fill.scss @@ -28,7 +28,7 @@ $mat-form-field-fill-subscript-padding: padding: $mat-form-field-fill-line-spacing $mat-form-field-fill-side-padding 0 $mat-form-field-fill-side-padding; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } @@ -46,7 +46,7 @@ $mat-form-field-fill-subscript-padding: bottom: 0; height: $mat-form-field-fill-underline-ripple-height; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: solid $mat-form-field-fill-underline-ripple-height; } diff --git a/src/material/form-field/form-field-input.scss b/src/material/form-field/form-field-input.scss index 532ee8d8bbc8..387263ffeced 100644 --- a/src/material/form-field/form-field-input.scss +++ b/src/material/form-field/form-field-input.scss @@ -181,7 +181,7 @@ select.mat-input-element { // as the background, however this causes it blend in because we've reset the `background` // above. We have to add a more specific selector in order to ensure that it gets the // `color` from our theme instead. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { .mat-focused & { color: inherit; } diff --git a/src/material/form-field/form-field-legacy.scss b/src/material/form-field/form-field-legacy.scss index 88cd1a3f1b38..aa8a2e200d75 100644 --- a/src/material/form-field/form-field-legacy.scss +++ b/src/material/form-field/form-field-legacy.scss @@ -38,7 +38,7 @@ $mat-form-field-legacy-underline-height: 1px !default; .mat-form-field-underline { height: $mat-form-field-legacy-underline-height; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: solid $mat-form-field-legacy-underline-height; } @@ -53,7 +53,7 @@ $mat-form-field-legacy-underline-height: 1px !default; // the desired form-field ripple height. See: angular/components#6351 overflow: hidden; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: solid $height; } @@ -63,7 +63,7 @@ $mat-form-field-legacy-underline-height: 1px !default; background-position: 0; background-color: transparent; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-top-style: dotted; border-top-width: 2px; } diff --git a/src/material/form-field/form-field-outline.scss b/src/material/form-field/form-field-outline.scss index badf1a5ca0bb..764c2765f4b2 100644 --- a/src/material/form-field/form-field-outline.scss +++ b/src/material/form-field/form-field-outline.scss @@ -81,7 +81,7 @@ $mat-form-field-outline-subscript-padding: } .mat-form-field-outline-gap { - // hack for Chrome's treatment of borders with non-integer (scaled) widths + // Workaround for Chrome's treatment of borders with non-integer (scaled) widths // refer to https://github.com/angular/components/issues/10710 border-radius: 0.000001px; @@ -103,7 +103,6 @@ $mat-form-field-outline-subscript-padding: .mat-form-field-outline-end, .mat-form-field-outline-gap { border-width: $mat-form-field-outline-thick-width; - transition: border-color 300ms $swift-ease-out-timing-function; } } diff --git a/src/material/form-field/form-field-standard.scss b/src/material/form-field/form-field-standard.scss index 16a8ce349c9f..ed9ec7311b6c 100644 --- a/src/material/form-field/form-field-standard.scss +++ b/src/material/form-field/form-field-standard.scss @@ -22,7 +22,7 @@ $mat-form-field-standard-padding-top: 0.75em !default; .mat-form-field-underline { height: $mat-form-field-standard-underline-height; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: solid $mat-form-field-standard-underline-height; } @@ -33,7 +33,7 @@ $mat-form-field-standard-padding-top: 0.75em !default; bottom: 0; height: $height; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; border-top: $height; } @@ -43,7 +43,7 @@ $mat-form-field-standard-padding-top: 0.75em !default; background-position: 0; background-color: transparent; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-top-style: dotted; border-top-width: 2px; } diff --git a/src/material/form-field/form-field.scss b/src/material/form-field/form-field.scss index 788edb103992..eb71e0ae4562 100644 --- a/src/material/form-field/form-field.scss +++ b/src/material/form-field/form-field.scss @@ -58,7 +58,7 @@ $mat-form-field-default-infix-width: 180px !default; // Since we can't remove the border altogether or replace it with a margin, because it'll throw // off the baseline, and we can't use a base64-encoded 1x1 transparent image because of CSP, // we work around it by setting a linear gradient that goes from `transparent` to `transparent`. - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-image: linear-gradient(transparent, transparent); } } diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index f24ae43a742e..c871a5eed5f1 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -30,7 +30,6 @@ import { } from '@angular/core'; import { CanColor, CanColorCtor, - FloatLabelType, LabelOptions, MAT_LABEL_GLOBAL_OPTIONS, mixinColor, @@ -75,14 +74,12 @@ class MatFormFieldBase { const _MatFormFieldMixinBase: CanColorCtor & typeof MatFormFieldBase = mixinColor(MatFormFieldBase, 'primary'); -/** - * Possible appearance styles for the form field. - * - * Note: The `legacy` and `standard` appearances are deprecated. Please use `fill` or `outline`. - * @breaking-change 11.0.0 Remove `legacy` and `standard`. - */ +/** Possible appearance styles for the form field. */ export type MatFormFieldAppearance = 'legacy' | 'standard' | 'fill' | 'outline'; +/** Possible values for the "floatLabel" form-field input. */ +export type FloatLabelType = 'always' | 'never' | 'auto'; + /** * Represents the default options for the form field that can be configured * using the `MAT_FORM_FIELD_DEFAULT_OPTIONS` injection token. @@ -90,6 +87,11 @@ export type MatFormFieldAppearance = 'legacy' | 'standard' | 'fill' | 'outline'; export interface MatFormFieldDefaultOptions { appearance?: MatFormFieldAppearance; hideRequiredMarker?: boolean; + /** + * Whether the label for form-fields should by default float `always`, + * `never`, or `auto` (only when necessary). + */ + floatLabel?: FloatLabelType; } /** @@ -227,7 +229,7 @@ export class MatFormField extends _MatFormFieldMixinBase } set floatLabel(value: FloatLabelType) { if (value !== this._floatLabel) { - this._floatLabel = value || this._labelOptions.float || 'auto'; + this._floatLabel = value || this._getDefaultFloatLabelState(); this._changeDetectorRef.markForCheck(); } } @@ -249,8 +251,8 @@ export class MatFormField extends _MatFormFieldMixinBase @ContentChild(MatFormFieldControl) _controlNonStatic: MatFormFieldControl; @ContentChild(MatFormFieldControl, {static: true}) _controlStatic: MatFormFieldControl; get _control() { - // TODO(crisbeto): we need this hacky workaround in order to support both Ivy - // and ViewEngine. We should clean this up once Ivy is the default renderer. + // TODO(crisbeto): we need this workaround in order to support both Ivy and ViewEngine. + // We should clean this up once Ivy is the default renderer. return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic; } set _control(value) { @@ -280,7 +282,7 @@ export class MatFormField extends _MatFormFieldMixinBase super(_elementRef); this._labelOptions = labelOptions ? labelOptions : {}; - this.floatLabel = this._labelOptions.float || 'auto'; + this.floatLabel = this._getDefaultFloatLabelState(); this._animationsEnabled = _animationMode !== 'NoopAnimations'; // Set the default through here so we invoke the setter on the first run. @@ -473,6 +475,11 @@ export class MatFormField extends _MatFormFieldMixinBase } } + /** Gets the default float label state. */ + private _getDefaultFloatLabelState(): FloatLabelType { + return (this._defaults && this._defaults.floatLabel) || this._labelOptions.float || 'auto'; + } + /** * Sets the list of element IDs that describe the child control. This allows the control to update * its `aria-describedby` attribute accordingly. @@ -568,10 +575,10 @@ export class MatFormField extends _MatFormFieldMixinBase } for (let i = 0; i < startEls.length; i++) { - startEls.item(i).style.width = `${startWidth}px`; + startEls[i].style.width = `${startWidth}px`; } for (let i = 0; i < gapEls.length; i++) { - gapEls.item(i).style.width = `${gapWidth}px`; + gapEls[i].style.width = `${gapWidth}px`; } this._outlineGapCalculationNeededOnStable = diff --git a/src/material/form-field/testing/form-field-harness.spec.ts b/src/material/form-field/testing/form-field-harness.spec.ts index 327102532604..7ce1d0d18068 100644 --- a/src/material/form-field/testing/form-field-harness.spec.ts +++ b/src/material/form-field/testing/form-field-harness.spec.ts @@ -1,15 +1,18 @@ -import {MatInputHarness} from '@angular/material/input/testing'; -import {MatSelectHarness} from '@angular/material/select/testing'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatInputModule} from '@angular/material/input'; +import {MatInputHarness} from '@angular/material/input/testing'; import {MatSelectModule} from '@angular/material/select'; +import {MatSelectHarness} from '@angular/material/select/testing'; import {MatFormFieldHarness} from './form-field-harness'; import {runHarnessTests} from './shared.spec'; describe('Non-MDC-based MatFormFieldHarness', () => { - runHarnessTests( - [MatFormFieldModule, MatAutocompleteModule, MatInputModule, MatSelectModule], - MatFormFieldHarness, MatInputHarness, MatSelectHarness); + runHarnessTests([MatFormFieldModule, MatAutocompleteModule, MatInputModule, MatSelectModule], { + formFieldHarness: MatFormFieldHarness, + inputHarness: MatInputHarness, + selectHarness: MatSelectHarness, + isMdcImplementation: false, + }); }); diff --git a/src/material/form-field/testing/form-field-harness.ts b/src/material/form-field/testing/form-field-harness.ts index 93162b46025f..3fba566c2ef1 100644 --- a/src/material/form-field/testing/form-field-harness.ts +++ b/src/material/form-field/testing/form-field-harness.ts @@ -113,11 +113,6 @@ export class MatFormFieldHarness extends ComponentHarness { return labelEl ? labelEl.text() : null; } - /** Whether the form-field has a floating label. */ - async hasFloatingLabel(): Promise { - return (await this.host()).hasClass('mat-form-field-can-float'); - } - /** Whether the form-field has errors. */ async hasErrors(): Promise { return (await this.getTextErrors()).length > 0; @@ -125,7 +120,13 @@ export class MatFormFieldHarness extends ComponentHarness { /** Whether the label is currently floating. */ async isLabelFloating(): Promise { - return (await this.host()).hasClass('mat-form-field-should-float'); + const [hasLabel, shouldFloat] = await Promise.all([ + this.hasLabel(), + (await this.host()).hasClass('mat-form-field-should-float'), + ]); + // If there is no label, the label conceptually can never float. The `should-float` class + // is just always set regardless of whether the label is displayed or not. + return hasLabel && shouldFloat; } /** Whether the form-field is disabled. */ diff --git a/src/material/form-field/testing/shared.spec.ts b/src/material/form-field/testing/shared.spec.ts index f43881800720..249d331ca34b 100644 --- a/src/material/form-field/testing/shared.spec.ts +++ b/src/material/form-field/testing/shared.spec.ts @@ -1,8 +1,4 @@ -import { - ComponentHarness, - HarnessLoader, - HarnessPredicate -} from '@angular/cdk/testing'; +import {ComponentHarness, HarnessLoader, HarnessPredicate} from '@angular/cdk/testing'; import { createFakeEvent, dispatchFakeEvent, @@ -16,8 +12,12 @@ import {MatFormFieldHarness} from './form-field-harness'; /** Shared tests to run on both the original and MDC-based form-field's. */ export function runHarnessTests( - modules: Type[], formFieldHarness: typeof MatFormFieldHarness, inputHarness: Type, - selectHarness: Type) { + modules: Type[], {formFieldHarness, inputHarness, selectHarness, isMdcImplementation}: { + formFieldHarness: typeof MatFormFieldHarness, + inputHarness: Type, + selectHarness: Type, + isMdcImplementation: boolean + }) { let fixture: ComponentFixture; let loader: HarnessLoader; @@ -36,7 +36,7 @@ export function runHarnessTests( it('should be able to load harnesses', async () => { const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(formFields.length).toBe(4); + expect(formFields.length).toBe(5); }); it('should be able to load form-field that matches specific selector', async () => { @@ -48,10 +48,11 @@ export function runHarnessTests( it('should be able to get appearance of form-field', async () => { const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(await formFields[0].getAppearance()).toBe('legacy'); - expect(await formFields[1].getAppearance()).toBe('standard'); + expect(await formFields[0].getAppearance()).toBe(isMdcImplementation ? 'fill' : 'legacy'); + expect(await formFields[1].getAppearance()).toBe(isMdcImplementation ? 'fill' : 'standard'); expect(await formFields[2].getAppearance()).toBe('fill'); expect(await formFields[3].getAppearance()).toBe('outline'); + expect(await formFields[4].getAppearance()).toBe(isMdcImplementation ? 'fill' : 'legacy'); }); it('should be able to get control of form-field', async () => { @@ -60,6 +61,7 @@ export function runHarnessTests( expect(await formFields[1].getControl() instanceof inputHarness).toBe(true); expect(await formFields[2].getControl() instanceof selectHarness).toBe(true); expect(await formFields[3].getControl() instanceof inputHarness).toBe(true); + expect(await formFields[4].getControl() instanceof inputHarness).toBe(true); }); it('should be able to get custom control of form-field', async () => { @@ -69,6 +71,7 @@ export function runHarnessTests( .toBe(true); expect(await formFields[2].getControl(CustomControlHarness)).toBe(null); expect(await formFields[3].getControl(CustomControlHarness)).toBe(null); + expect(await formFields[4].getControl(CustomControlHarness)).toBe(null); }); it('should be able to get custom control of form-field using a predicate', async () => { @@ -78,39 +81,34 @@ export function runHarnessTests( expect(await formFields[1].getControl(predicate) instanceof CustomControlHarness).toBe(true); expect(await formFields[2].getControl(predicate)).toBe(null); expect(await formFields[3].getControl(predicate)).toBe(null); + expect(await formFields[4].getControl(predicate)).toBe(null); }); it('should be able to check whether form-field has label', async () => { const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(await formFields[0].hasLabel()).toBe(true); + // The non MDC-based form-field elevates the placeholder to a floating + // label. This is not the case in the MDC-based implementation. + expect(await formFields[0].hasLabel()).toBe(!isMdcImplementation); expect(await formFields[1].hasLabel()).toBe(false); expect(await formFields[2].hasLabel()).toBe(true); expect(await formFields[3].hasLabel()).toBe(true); - - fixture.componentInstance.hasLabel = true; - expect(await formFields[1].hasLabel()).toBe(true); - }); - - it('should be able to check whether form-field has floating label', async () => { - const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(await formFields[0].hasFloatingLabel()).toBe(false); - expect(await formFields[1].hasFloatingLabel()).toBe(true); - expect(await formFields[2].hasFloatingLabel()).toBe(true); - expect(await formFields[3].hasFloatingLabel()).toBe(true); - - fixture.componentInstance.shouldLabelFloat = 'auto'; - expect(await formFields[0].hasFloatingLabel()).toBe(true); + expect(await formFields[4].hasLabel()).toBe(true); }); it('should be able to check whether label is floating', async () => { const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(await formFields[0].isLabelFloating()).toBe(false); - expect(await formFields[1].isLabelFloating()).toBe(true); + // The first form-field uses the legacy appearance. This means that the + // placeholder will be elevated to a label. Also since there is a static + // value, the label will float initially. The MDC implementation does not + // elevate placeholders to floating labels. + expect(await formFields[0].isLabelFloating()).toBe(!isMdcImplementation); + expect(await formFields[1].isLabelFloating()).toBe(false); expect(await formFields[2].isLabelFloating()).toBe(false); expect(await formFields[3].isLabelFloating()).toBe(true); + expect(await formFields[4].isLabelFloating()).toBe(false); fixture.componentInstance.shouldLabelFloat = 'always'; - expect(await formFields[0].hasFloatingLabel()).toBe(true); + expect(await formFields[4].isLabelFloating()).toBe(true); }); it('should be able to check whether form-field is disabled', async () => { @@ -119,12 +117,14 @@ export function runHarnessTests( expect(await formFields[1].isDisabled()).toBe(false); expect(await formFields[2].isDisabled()).toBe(false); expect(await formFields[3].isDisabled()).toBe(false); + expect(await formFields[4].isDisabled()).toBe(false); fixture.componentInstance.isDisabled = true; expect(await formFields[0].isDisabled()).toBe(true); expect(await formFields[1].isDisabled()).toBe(false); expect(await formFields[2].isDisabled()).toBe(true); expect(await formFields[3].isDisabled()).toBe(false); + expect(await formFields[4].isDisabled()).toBe(false); }); it('should be able to check whether form-field is auto-filled', async () => { @@ -133,6 +133,7 @@ export function runHarnessTests( expect(await formFields[1].isAutofilled()).toBe(false); expect(await formFields[2].isAutofilled()).toBe(false); expect(await formFields[3].isAutofilled()).toBe(false); + expect(await formFields[4].isAutofilled()).toBe(false); const autofillTriggerEvent: any = createFakeEvent('animationstart'); autofillTriggerEvent.animationName = 'cdk-text-field-autofill-start'; @@ -146,6 +147,7 @@ export function runHarnessTests( expect(await formFields[1].isAutofilled()).toBe(false); expect(await formFields[2].isAutofilled()).toBe(false); expect(await formFields[3].isAutofilled()).toBe(false); + expect(await formFields[4].isAutofilled()).toBe(false); }); it('should be able to get theme color of form-field', async () => { @@ -154,14 +156,18 @@ export function runHarnessTests( expect(await formFields[1].getThemeColor()).toBe('warn'); expect(await formFields[2].getThemeColor()).toBe('accent'); expect(await formFields[3].getThemeColor()).toBe('primary'); + expect(await formFields[4].getThemeColor()).toBe('primary'); }); it('should be able to get label of form-field', async () => { const formFields = await loader.getAllHarnesses(formFieldHarness); - expect(await formFields[0].getLabel()).toBe('With placeholder'); + // In the MDC based implementation, the placeholder will not be elevated + // to a label, and the harness will return `null`. + expect(await formFields[0].getLabel()).toBe(isMdcImplementation ? null : 'With placeholder'); expect(await formFields[1].getLabel()).toBe(null); expect(await formFields[2].getLabel()).toBe('Label'); expect(await formFields[3].getLabel()).toBe('autocomplete_label'); + expect(await formFields[4].getLabel()).toBe('Label'); }); it('should be able to get error messages of form-field', async () => { @@ -250,7 +256,6 @@ export function runHarnessTests( Custom control harness - Second input label Error 1 @@ -274,11 +279,16 @@ export function runHarnessTests( autocomplete_option + + + Label + + ` }) class FormFieldHarnessTest { requiredControl = new FormControl('Initial value', [Validators.required]); - shouldLabelFloat: 'never'|'auto'|'always' = 'never'; + shouldLabelFloat: 'always'|'auto' = 'auto'; hasLabel = false; isDisabled = false; diff --git a/src/material/grid-list/grid-list.ts b/src/material/grid-list/grid-list.ts index 5afdced74b6d..8b05f4b809a1 100644 --- a/src/material/grid-list/grid-list.ts +++ b/src/material/grid-list/grid-list.ts @@ -39,6 +39,9 @@ const MAT_FIT_MODE = 'fit'; styleUrls: ['grid-list.css'], host: { 'class': 'mat-grid-list', + // Ensures that the "cols" input value is reflected in the DOM. This is + // needed for the grid-list harness. + '[attr.cols]': 'cols', }, providers: [{ provide: MAT_GRID_LIST, diff --git a/src/material/grid-list/grid-tile.ts b/src/material/grid-list/grid-tile.ts index f3072aec0435..486a15441a8a 100644 --- a/src/material/grid-list/grid-tile.ts +++ b/src/material/grid-list/grid-tile.ts @@ -28,6 +28,10 @@ import {MAT_GRID_LIST, MatGridListBase} from './grid-list-base'; exportAs: 'matGridTile', host: { 'class': 'mat-grid-tile', + // Ensures that the "rowspan" and "colspan" input value is reflected in + // the DOM. This is needed for the grid-tile harness. + '[attr.rowspan]': 'rowspan', + '[attr.colspan]': 'colspan' }, templateUrl: 'grid-tile.html', styleUrls: ['grid-list.css'], diff --git a/src/material/grid-list/public-api.ts b/src/material/grid-list/public-api.ts index 2360f7283514..e93428bc5b85 100644 --- a/src/material/grid-list/public-api.ts +++ b/src/material/grid-list/public-api.ts @@ -10,3 +10,5 @@ export * from './grid-list-module'; export * from './grid-list'; export * from './grid-tile'; +// Privately exported for the grid-list harness. +export {TileCoordinator as ɵTileCoordinator} from './tile-coordinator'; diff --git a/src/material/grid-list/testing/BUILD.bazel b/src/material/grid-list/testing/BUILD.bazel new file mode 100644 index 000000000000..638e90143165 --- /dev/null +++ b/src/material/grid-list/testing/BUILD.bazel @@ -0,0 +1,52 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material/grid-list/testing", + deps = [ + "//src/cdk/testing", + "//src/material/grid-list", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "harness_tests_lib", + srcs = ["shared.spec.ts"], + deps = [ + ":testing", + "//src/cdk/testing", + "//src/cdk/testing/private", + "//src/cdk/testing/testbed", + "//src/material/grid-list", + "@npm//@angular/platform-browser", + ], +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["shared.spec.ts"], + ), + deps = [ + ":harness_tests_lib", + ":testing", + "//src/material/grid-list", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_tests_lib"], +) diff --git a/src/material/grid-list/testing/grid-list-harness-filters.ts b/src/material/grid-list/testing/grid-list-harness-filters.ts new file mode 100644 index 000000000000..819966bdf109 --- /dev/null +++ b/src/material/grid-list/testing/grid-list-harness-filters.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 {BaseHarnessFilters} from '@angular/cdk/testing'; + +/** A set of criteria that can be used to filter a list of `MatGridListHarness` instances. */ +export interface GridListHarnessFilters extends BaseHarnessFilters {} + +/** A set of criteria that can be used to filter a list of `MatTileHarness` instances. */ +export interface GridTileHarnessFilters extends BaseHarnessFilters { + /** Text the grid-tile header should match. */ + headerText?: string|RegExp; + /** Text the grid-tile footer should match. */ + footerText?: string|RegExp; +} diff --git a/src/material/grid-list/testing/grid-list-harness.spec.ts b/src/material/grid-list/testing/grid-list-harness.spec.ts new file mode 100644 index 000000000000..7631e80a54ad --- /dev/null +++ b/src/material/grid-list/testing/grid-list-harness.spec.ts @@ -0,0 +1,8 @@ +import {MatGridListModule} from '@angular/material/grid-list'; +import {MatGridListHarness} from './grid-list-harness'; +import {MatGridTileHarness} from './grid-tile-harness'; +import {runHarnessTests} from './shared.spec'; + +describe('Non-MDC-based MatGridListHarness', () => { + runHarnessTests(MatGridListModule, MatGridListHarness, MatGridTileHarness); +}); diff --git a/src/material/grid-list/testing/grid-list-harness.ts b/src/material/grid-list/testing/grid-list-harness.ts new file mode 100644 index 000000000000..c656bfe415b7 --- /dev/null +++ b/src/material/grid-list/testing/grid-list-harness.ts @@ -0,0 +1,76 @@ +/** + * @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 {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {ɵTileCoordinator as TileCoordinator} from '@angular/material/grid-list'; +import {GridListHarnessFilters, GridTileHarnessFilters} from './grid-list-harness-filters'; +import {MatGridTileHarness} from './grid-tile-harness'; + +/** Harness for interacting with a standard `MatGridList` in tests. */ +export class MatGridListHarness extends ComponentHarness { + /** The selector for the host element of a `MatGridList` instance. */ + static hostSelector = '.mat-grid-list'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatGridListHarness` + * that meets certain criteria. + * @param options Options for filtering which dialog instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: GridListHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatGridListHarness, options); + } + + /** + * Tile coordinator that is used by the "MatGridList" for computing + * positions of tiles. We leverage the coordinator to provide an API + * for retrieving tiles based on visual tile positions. + */ + private _tileCoordinator = new TileCoordinator(); + + /** Gets all tiles of the grid-list. */ + async getTiles(filters: GridTileHarnessFilters = {}): Promise { + return await this.locatorForAll(MatGridTileHarness.with(filters))(); + } + + /** Gets the amount of columns of the grid-list. */ + async getColumns(): Promise { + return Number(await (await this.host()).getAttribute('cols')); + } + + /** + * Gets a tile of the grid-list that is located at the given location. + * @param row Zero-based row index. + * @param column Zero-based column index. + */ + async getTileAtPosition({row, column}: {row: number, column: number}): + Promise { + const [tileHarnesses, columns] = await Promise.all([this.getTiles(), this.getColumns()]); + const tileSpans = tileHarnesses.map(t => Promise.all([t.getColspan(), t.getRowspan()])); + const tiles = (await Promise.all(tileSpans)).map(([colspan, rowspan]) => ({colspan, rowspan})); + // Update the tile coordinator to reflect the current column amount and + // rendered tiles. We update upon every call of this method since we do not + // know if tiles have been added, removed or updated (in terms of rowspan/colspan). + this._tileCoordinator.update(columns, tiles); + // The tile coordinator respects the colspan and rowspan for calculating the positions + // of tiles, but it does not create multiple position entries if a tile spans over multiple + // columns or rows. We want to provide an API where developers can retrieve a tile based on + // any position that lies within the visual tile boundaries. For example: If a tile spans + // over two columns, then the same tile should be returned for either column indices. + for (let i = 0; i < this._tileCoordinator.positions.length; i++) { + const position = this._tileCoordinator.positions[i]; + const {rowspan, colspan} = tiles[i]; + // Return the tile harness if the given position visually resolves to the tile. + if (column >= position.col && column <= position.col + colspan - 1 && row >= position.row && + row <= position.row + rowspan - 1) { + return tileHarnesses[i]; + } + } + throw Error('Could not find tile at given position.'); + } +} diff --git a/src/material/grid-list/testing/grid-tile-harness.ts b/src/material/grid-list/testing/grid-tile-harness.ts new file mode 100644 index 000000000000..28f554657c3e --- /dev/null +++ b/src/material/grid-list/testing/grid-tile-harness.ts @@ -0,0 +1,77 @@ +/** + * @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 {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {GridTileHarnessFilters} from './grid-list-harness-filters'; + +/** Harness for interacting with a standard `MatGridTitle` in tests. */ +export class MatGridTileHarness extends ComponentHarness { + /** The selector for the host element of a `MatGridTile` instance. */ + static hostSelector = '.mat-grid-tile'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatGridTileHarness` + * that meets certain criteria. + * @param options Options for filtering which dialog instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: GridTileHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatGridTileHarness, options) + .addOption( + 'headerText', options.headerText, + (harness, pattern) => HarnessPredicate.stringMatches(harness.getHeaderText(), pattern)) + .addOption( + 'footerText', options.footerText, + (harness, pattern) => HarnessPredicate.stringMatches(harness.getFooterText(), pattern)); + } + + private _header = this.locatorForOptional('.mat-grid-tile-header'); + private _footer = this.locatorForOptional('.mat-grid-tile-footer'); + private _avatar = this.locatorForOptional('.mat-grid-avatar'); + + /** Gets the amount of rows that the grid-tile takes up. */ + async getRowspan(): Promise { + return Number(await (await this.host()).getAttribute('rowspan')); + } + + /** Gets the amount of columns that the grid-tile takes up. */ + async getColspan(): Promise { + return Number(await (await this.host()).getAttribute('colspan')); + } + + /** Whether the grid-tile has a header. */ + async hasHeader(): Promise { + return (await this._header()) !== null; + } + + /** Whether the grid-tile has a footer. */ + async hasFooter(): Promise { + return (await this._footer()) !== null; + } + + /** Whether the grid-tile has an avatar. */ + async hasAvatar(): Promise { + return (await this._avatar()) !== null; + } + + /** Gets the text of the header if present. */ + async getHeaderText(): Promise { + // For performance reasons, we do not use "hasHeader" as + // we would then need to query twice for the header. + const headerEl = await this._header(); + return headerEl ? headerEl.text() : null; + } + + /** Gets the text of the footer if present. */ + async getFooterText(): Promise { + // For performance reasons, we do not use "hasFooter" as + // we would then need to query twice for the footer. + const headerEl = await this._footer(); + return headerEl ? headerEl.text() : null; + } +} diff --git a/src/material/grid-list/testing/index.ts b/src/material/grid-list/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material/grid-list/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/grid-list/testing/public-api.ts b/src/material/grid-list/testing/public-api.ts new file mode 100644 index 000000000000..63f962101e6b --- /dev/null +++ b/src/material/grid-list/testing/public-api.ts @@ -0,0 +1,11 @@ +/** + * @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 './grid-tile-harness'; +export * from './grid-list-harness'; +export * from './grid-list-harness-filters'; diff --git a/src/material/grid-list/testing/shared.spec.ts b/src/material/grid-list/testing/shared.spec.ts new file mode 100644 index 000000000000..b831636b29af --- /dev/null +++ b/src/material/grid-list/testing/shared.spec.ts @@ -0,0 +1,194 @@ +import {HarnessLoader} from '@angular/cdk/testing'; +import {expectAsyncError} from '@angular/cdk/testing/private'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {Component} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {MatGridListModule} from '@angular/material/grid-list'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {MatGridListHarness} from './grid-list-harness'; +import {MatGridTileHarness} from './grid-tile-harness'; + +/** Shared tests to run on both the original and MDC-based grid-list. */ +export function runHarnessTests( + gridListModule: typeof MatGridListModule, gridListHarness: typeof MatGridListHarness, + gridTileHarness: typeof MatGridTileHarness) { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + await TestBed + .configureTestingModule({ + imports: [gridListModule, NoopAnimationsModule], + declarations: [GridListHarnessTest], + }) + .compileComponents(); + + fixture = TestBed.createComponent(GridListHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); + }); + + it('should be able to load grid-list harnesses', async () => { + const harnesses = await loader.getAllHarnesses(gridListHarness); + expect(harnesses.length).toBe(2); + }); + + it('should be able to load grid-tile harnesses', async () => { + const harnesses = await loader.getAllHarnesses(gridTileHarness); + expect(harnesses.length).toBe(8); + }); + + it('should be able to load grid-tile harness by header text', async () => { + const harnesses = await loader.getAllHarnesses(gridTileHarness.with({headerText: /Tile 3/})); + expect(harnesses.length).toBe(1); + expect(await harnesses[0].getFooterText()).toBe('Tile 3 footer'); + }); + + it('should be able to load grid-tile harness by footer text', async () => { + const harnesses = + await loader.getAllHarnesses(gridTileHarness.with({footerText: 'Tile 3 footer'})); + expect(harnesses.length).toBe(1); + expect(await harnesses[0].getHeaderText()).toBe('Tile 3'); + }); + + it('should be able to get all tiles of a grid-list', async () => { + const gridList = await loader.getHarness(gridListHarness.with({selector: '#second'})); + const tiles = await gridList.getTiles(); + expect(tiles.length).toBe(4); + }); + + it('should be able to get tiles of grid-list with filters', async () => { + const gridList = await loader.getHarness(gridListHarness.with({selector: '#second'})); + const tiles = await gridList.getTiles({headerText: /Tile [34]/}); + expect(tiles.length).toBe(2); + }); + + it('should be able to get amount of columns of grid-list', async () => { + const gridLists = await loader.getAllHarnesses(gridListHarness); + expect(await gridLists[0].getColumns()).toBe(4); + expect(await gridLists[1].getColumns()).toBe(2); + fixture.componentInstance.columns = 3; + expect(await gridLists[0].getColumns()).toBe(3); + }); + + it('should be able to get tile by position', async () => { + const gridList = await loader.getHarness(gridListHarness); + const tiles = await Promise.all([ + gridList.getTileAtPosition({row: 0, column: 0}), + gridList.getTileAtPosition({row: 0, column: 1}), + gridList.getTileAtPosition({row: 1, column: 0}), + ]); + expect(await tiles[0].getHeaderText()).toBe('One'); + expect(await tiles[1].getHeaderText()).toBe('Two'); + expect(await tiles[2].getHeaderText()).toBe('Four (second row)'); + }); + + it('should be able to get tile by position with respect to tile span', async () => { + const gridList = await loader.getHarness(gridListHarness); + const tiles = await Promise.all([ + gridList.getTileAtPosition({row: 0, column: 2}), + gridList.getTileAtPosition({row: 0, column: 3}), + ]); + expect(await tiles[0].getHeaderText()).toBe('Three'); + expect(await tiles[1].getHeaderText()).toBe('Three'); + await expectAsyncError( + () => gridList.getTileAtPosition({row: 2, column: 0}), /Could not find tile/); + + // Update the fourth tile to span over two rows. The previous position + // should now be valid and the fourth tile should be returned. + fixture.componentInstance.tile4Rowspan = 2; + const foundTile = await gridList.getTileAtPosition({row: 2, column: 0}); + expect(await foundTile.getHeaderText()).toBe('Four (second and third row)'); + }); + + it('should be able to check whether tile has header', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].hasHeader()).toBe(true); + expect(await tiles[4].hasHeader()).toBe(false); + expect(await (await tiles[4].host()).text()).toBe('Tile 1 (no header, no footer)'); + }); + + it('should be able to check whether tile has footer', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].hasFooter()).toBe(false); + expect(await tiles[6].hasFooter()).toBe(true); + expect(await tiles[6].getFooterText()).toBe('Tile 3 footer'); + }); + + it('should be able to check whether tile has avatar', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].hasAvatar()).toBe(false); + expect(await tiles[1].hasAvatar()).toBe(true); + }); + + it('should be able to get rowspan of tile', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].getRowspan()).toBe(1); + expect(await tiles[3].getRowspan()).toBe(1); + fixture.componentInstance.tile4Rowspan = 10; + expect(await tiles[3].getRowspan()).toBe(10); + }); + + it('should be able to get colspan of tile', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].getColspan()).toBe(1); + expect(await tiles[2].getColspan()).toBe(2); + }); + + it('should be able to get header text of tile', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].getHeaderText()).toBe('One'); + fixture.componentInstance.firstTileText = 'updated'; + expect(await tiles[0].getHeaderText()).toBe('updated'); + }); + + it('should be able to get footer text of tile', async () => { + const tiles = await loader.getAllHarnesses(gridTileHarness); + expect(await tiles[0].getFooterText()).toBe(null); + fixture.componentInstance.showFooter = true; + expect(await tiles[0].getFooterText()).toBe('Footer'); + }); +} + +@Component({ + template: ` + + + {{firstTileText}} + Footer + + + Two +
    +
    + + Three + + + + Four (second {{tile4Rowspan === 2 ? 'and third ' : ''}}row) + + +
    + + + Tile 1 (no header, no footer) + + Tile 2 + + + Tile 3 + Tile 3 footer + + + Tile 4 + + + ` +}) +class GridListHarnessTest { + firstTileText = 'One'; + showFooter = false; + columns = 4; + tile4Rowspan = 1; +} diff --git a/src/material/grid-list/tile-coordinator.ts b/src/material/grid-list/tile-coordinator.ts index e0c36b5e9b4f..febec1e87f71 100644 --- a/src/material/grid-list/tile-coordinator.ts +++ b/src/material/grid-list/tile-coordinator.ts @@ -6,7 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {MatGridTile} from './grid-tile'; +/** + * Interface describing a tile. + * @docs-private + */ +export interface Tile { + /** Amount of rows that the tile takes up. */ + rowspan: number; + /** Amount of columns that the tile takes up. */ + colspan: number; +} /** * Class for determining, from a list of tiles, the (row, col) position of each of those tiles @@ -55,8 +64,9 @@ export class TileCoordinator { /** * Updates the tile positions. * @param numColumns Amount of columns in the grid. + * @param tiles Tiles to be positioned. */ - update(numColumns: number, tiles: MatGridTile[]) { + update(numColumns: number, tiles: Tile[]) { this.columnIndex = 0; this.rowIndex = 0; @@ -66,7 +76,7 @@ export class TileCoordinator { } /** Calculates the row and col position of a tile. */ - private _trackTile(tile: MatGridTile): TilePosition { + private _trackTile(tile: Tile): TilePosition { // Find a gap large enough for this tile. const gapStartIndex = this._findMatchingGap(tile.colspan); @@ -153,7 +163,7 @@ export class TileCoordinator { } /** Update the tile tracker to account for the given tile in the given space. */ - private _markTilePosition(start: number, tile: MatGridTile): void { + private _markTilePosition(start: number, tile: Tile): void { for (let i = 0; i < tile.colspan; i++) { this.tracker[start + i] = tile.rowspan; } diff --git a/src/material/icon/icon.md b/src/material/icon/icon.md index 11f99cf67167..3912ae273eb8 100644 --- a/src/material/icon/icon.md +++ b/src/material/icon/icon.md @@ -37,20 +37,25 @@ explicitly set by calling `MatIconRegistry.setDefaultFontSetClass`. ### SVG icons -When an `mat-icon` component displays an SVG icon, it does so by directly inlining the SVG content -into the page as a child of the component. (Rather than using an `` tag or a div background -image). This makes it easier to apply CSS styles to SVG icons. For example, the default color of the +`` displays SVG icons by directly inlining the SVG content into the DOM +as a child of itself. This approach offers an advantage over an `` tag or a CSS +`background-image` because it allows styling the SVG with CSS. For example, the default color of the SVG content is the CSS [currentColor](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentColor_keyword) value. This makes SVG icons by default have the same color as surrounding text, and allows you to -change the color by setting the "color" style on the `mat-icon` element. +change the color by setting the `color` style on the `mat-icon` element. -In order to prevent XSS vulnerabilities, any SVG URLs and HTML strings passed to the +In order to guard against XSS vulnerabilities, any SVG URLs and HTML strings passed to the `MatIconRegistry` must be marked as trusted by using Angular's `DomSanitizer` service. -Also note that all SVG icons, registered by URL, are fetched via XmlHttpRequest, and due to the -same-origin policy, their URLs must be on the same domain as the containing page, or their servers -must be configured to allow cross-domain access. +`MatIconRegistry` fetches all remote SVG icons via Angular's `HttpClient` service. If you haven't +included [`HttpClientModule` from the `@angular/common/http` package](https://angular.io/guide/http) +in your `NgModule` imports, you will get an error at runtime. + +Note that `HttpClient` fetches SVG icons registered with a URL via `XmlHttpRequest`, subject to the +[Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). This +means that icon URLs must have the same origin as the containing page or that the application's +server must be configured to allow cross-origin requests. #### Named icons @@ -86,14 +91,14 @@ match the current theme's colors using the `color` attribute. This can be change Similar to an `` element, an icon alone does not convey any useful information for a screen-reader user. The user of `` must provide additional information pertaining to how the icon is used. Based on the use-cases described below, `mat-icon` is marked as -`aria-hidden="true"` by default, but this can be overriden by adding `aria-hidden="false"` to the +`aria-hidden="true"` by default, but this can be overridden by adding `aria-hidden="false"` to the element. In thinking about accessibility, it is useful to place icon use into one of three categories: 1. **Decorative**: the icon conveys no real semantic meaning and is purely cosmetic. 2. **Interactive**: a user will click or otherwise interact with the icon to perform some action. -3. **Indicator**: the icon is not interactive, but it conveys some information, such as a status. This -includes using the icon in place of text inside of a larger message. +3. **Indicator**: the icon is not interactive, but it conveys some information, such as a status. +This includes using the icon in place of text inside of a larger message. #### Decorative icons When the icon is purely cosmetic and conveys no real semantic meaning, the `` element @@ -107,8 +112,8 @@ some icon on the page, a more appropriate element should "own" the interaction: direct text content, `aria-label`, or `aria-labelledby`. #### Indicator icons -When the presence of an icon communicates some information to the user whether as an indicator or -by being inlined into a block of text, that information must also be made available to +When the presence of an icon communicates some information to the user whether as an indicator or +by being inlined into a block of text, that information must also be made available to screen-readers. The most straightforward way to do this is to 1. Add a `` as an adjacent sibling to the `` element with text that conveys the same information as the icon. @@ -118,7 +123,7 @@ on-screen but still available to screen-reader users. ### Bidirectionality By default icons in an RTL layout will look exactly the same as in LTR, however certain icons have -to be [mirrored for RTL users](https://material.io/guidelines/usability/bidirectionality.html). If +to be [mirrored for RTL users](https://material.io/design/usability/bidirectionality.html). If you want to mirror an icon only in an RTL layout, you can use the `mat-icon-rtl-mirror` CSS class. ```html diff --git a/src/material/icon/testing/BUILD.bazel b/src/material/icon/testing/BUILD.bazel new file mode 100644 index 000000000000..71a160e024eb --- /dev/null +++ b/src/material/icon/testing/BUILD.bazel @@ -0,0 +1,25 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module") + +ng_module( + name = "testing", + srcs = [ + "fake-icon-registry.ts", + "index.ts", + "public-api.ts", + ], + module_name = "@angular/material/icon/testing", + deps = [ + "//src/cdk/coercion", + "//src/material/icon", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//rxjs", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) diff --git a/src/material/icon/testing/fake-icon-registry.ts b/src/material/icon/testing/fake-icon-registry.ts new file mode 100644 index 000000000000..e6cfd68f33b7 --- /dev/null +++ b/src/material/icon/testing/fake-icon-registry.ts @@ -0,0 +1,101 @@ +/** + * @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 {Injectable, NgModule, OnDestroy} from '@angular/core'; +import {MatIconRegistry} from '@angular/material/icon'; +import {Observable, of as observableOf} from 'rxjs'; + +// tslint:disable:no-any Impossible to tell param types. +type PublicApi = { + [K in keyof T]: T[K] extends (...x: any[]) => T ? (...x: any[]) => PublicApi : T[K] +}; +// tslint:enable:no-any + +/** + * A null icon registry that must be imported to allow disabling of custom + * icons. + */ +@Injectable() +export class FakeMatIconRegistry implements PublicApi, OnDestroy { + addSvgIcon(): this { + return this; + } + + addSvgIconLiteral(): this { + return this; + } + + addSvgIconInNamespace(): this { + return this; + } + + addSvgIconLiteralInNamespace(): this { + return this; + } + + addSvgIconSet(): this { + return this; + } + + addSvgIconSetLiteral(): this { + return this; + } + + addSvgIconSetInNamespace(): this { + return this; + } + + addSvgIconSetLiteralInNamespace(): this { + return this; + } + + registerFontClassAlias(): this { + return this; + } + + classNameForFontAlias(alias: string): string { + return alias; + } + + getDefaultFontSetClass() { + return 'material-icons'; + } + + getSvgIconFromUrl(): Observable { + return observableOf(this._generateEmptySvg()); + } + + getNamedSvgIcon(): Observable { + return observableOf(this._generateEmptySvg()); + } + + setDefaultFontSetClass(): this { + return this; + } + + ngOnDestroy() { } + + private _generateEmptySvg(): SVGElement { + const emptySvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + emptySvg.classList.add('fake-testing-svg'); + // Emulate real icon characteristics from `MatIconRegistry` so size remains consistent in tests. + emptySvg.setAttribute('fit', ''); + emptySvg.setAttribute('height', '100%'); + emptySvg.setAttribute('width', '100%'); + emptySvg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); + emptySvg.setAttribute('focusable', 'false'); + return emptySvg; + } +} + +/** Import this module in tests to install the null icon registry. */ +@NgModule({ + providers: [{provide: MatIconRegistry, useClass: FakeMatIconRegistry}] +}) +export class MatIconTestingModule { +} diff --git a/src/material/icon/testing/index.ts b/src/material/icon/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material/icon/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/icon/testing/public-api.ts b/src/material/icon/testing/public-api.ts new file mode 100644 index 000000000000..16d338442041 --- /dev/null +++ b/src/material/icon/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 './fake-icon-registry'; diff --git a/src/material/input/BUILD.bazel b/src/material/input/BUILD.bazel index ce437f444360..c9e55f028738 100644 --- a/src/material/input/BUILD.bazel +++ b/src/material/input/BUILD.bazel @@ -25,7 +25,6 @@ ng_module( "//src/cdk/text-field", "//src/material/core", "//src/material/form-field", - "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/forms", "@npm//rxjs", diff --git a/src/material/input/autosize.ts b/src/material/input/autosize.ts index 438f2d804577..8b7d82af5457 100644 --- a/src/material/input/autosize.ts +++ b/src/material/input/autosize.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput, NumberInput} from '@angular/cdk/coercion'; import {CdkTextareaAutosize} from '@angular/cdk/text-field'; import {Directive, Input} from '@angular/core'; @@ -42,8 +41,4 @@ export class MatTextareaAutosize extends CdkTextareaAutosize { @Input() get matTextareaAutosize(): boolean { return this.enabled; } set matTextareaAutosize(value: boolean) { this.enabled = value; } - - static ngAcceptInputType_minRows: NumberInput; - static ngAcceptInputType_maxRows: NumberInput; - static ngAcceptInputType_enabled: BooleanInput; } diff --git a/src/material/input/input-module.ts b/src/material/input/input-module.ts index d2a8c27f57b5..4dc21e9885a5 100644 --- a/src/material/input/input-module.ts +++ b/src/material/input/input-module.ts @@ -7,18 +7,15 @@ */ import {TextFieldModule} from '@angular/cdk/text-field'; -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {ErrorStateMatcher} from '@angular/material/core'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatTextareaAutosize} from './autosize'; import {MatInput} from './input'; - @NgModule({ declarations: [MatInput, MatTextareaAutosize], imports: [ - CommonModule, TextFieldModule, MatFormFieldModule, ], diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 64a966f00503..2e72d61d464d 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -61,7 +61,7 @@ describe('MatInput without forms', () => { 'Expected MatInput to set floatingLabel to auto by default.'); })); - it('should default to global floating label type', fakeAsync(() => { + it('should default to floating label type from deprecated global label options', fakeAsync(() => { let fixture = createComponent(MatInputWithId, [{ provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: {float: 'always'} }]); @@ -73,6 +73,18 @@ describe('MatInput without forms', () => { 'Expected MatInput to set floatingLabel to always from global option.'); })); + it('should default to floating label type provided by global default options', fakeAsync(() => { + let fixture = createComponent(MatInputWithId, [{ + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {floatLabel: 'always'} + }]); + fixture.detectChanges(); + + let formField = fixture.debugElement.query(By.directive(MatFormField))! + .componentInstance as MatFormField; + expect(formField.floatLabel).toBe('always', + 'Expected MatInput to set floatingLabel to always from global option.'); + })); + it('should not be treated as empty if type is date', fakeAsync(() => { const platform = new Platform(); diff --git a/src/material/input/input.ts b/src/material/input/input.ts index ed1d2ca176e0..2363b4046cd3 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -332,6 +332,11 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl< // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events. } + /** Determines if the component host is a textarea. */ + _isTextarea() { + return this._elementRef.nativeElement.nodeName.toLowerCase() === 'textarea'; + } + /** Does some manual dirty checking on the native input `value` property. */ protected _dirtyCheckNativeValue() { const newValue = this._elementRef.nativeElement.value; @@ -361,11 +366,6 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl< return validity && validity.badInput; } - /** Determines if the component host is a textarea. */ - protected _isTextarea() { - return this._elementRef.nativeElement.nodeName.toLowerCase() === 'textarea'; - } - /** * Implemented as part of MatFormFieldControl. * @docs-private diff --git a/src/material/input/testing/input-harness.ts b/src/material/input/testing/input-harness.ts index ce982f6119e0..c74ea2476bae 100644 --- a/src/material/input/testing/input-harness.ts +++ b/src/material/input/testing/input-harness.ts @@ -12,7 +12,10 @@ import {InputHarnessFilters} from './input-harness-filters'; /** Harness for interacting with a standard Material inputs in tests. */ export class MatInputHarness extends MatFormFieldControlHarness { - static hostSelector = '[matInput]'; + // TODO: We do not want to handle `select` elements with `matNativeControl` because + // not all methods of this harness work reasonably for native select elements. + // For more details. See: https://github.com/angular/components/pull/18221. + static hostSelector = '[matInput], input[matNativeControl], textarea[matNativeControl]'; /** * Gets a `HarnessPredicate` that can be used to search for a `MatInputHarness` that meets diff --git a/src/material/input/testing/shared.spec.ts b/src/material/input/testing/shared.spec.ts index 058720daf0a7..0a405f88520d 100644 --- a/src/material/input/testing/shared.spec.ts +++ b/src/material/input/testing/shared.spec.ts @@ -28,7 +28,7 @@ export function runHarnessTests( it('should load all input harnesses', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); }); it('should load input with specific id', async () => { @@ -49,48 +49,62 @@ export function runHarnessTests( it('should be able to get id of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getId()).toMatch(/mat-input-\d+/); expect(await inputs[1].getId()).toMatch(/mat-input-\d+/); expect(await inputs[2].getId()).toBe('myTextarea'); + expect(await inputs[3].getId()).toBe('nativeControl'); + expect(await inputs[4].getId()).toMatch(/mat-input-\d+/); }); it('should be able to get name of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getName()).toBe('favorite-food'); expect(await inputs[1].getName()).toBe(''); expect(await inputs[2].getName()).toBe(''); + expect(await inputs[3].getName()).toBe(''); + expect(await inputs[4].getName()).toBe(''); }); it('should be able to get value of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getValue()).toBe('Sushi'); expect(await inputs[1].getValue()).toBe(''); expect(await inputs[2].getValue()).toBe(''); + expect(await inputs[3].getValue()).toBe(''); + expect(await inputs[4].getValue()).toBe(''); }); it('should be able to set value of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getValue()).toBe('Sushi'); expect(await inputs[1].getValue()).toBe(''); + expect(await inputs[3].getValue()).toBe(''); + expect(await inputs[4].getValue()).toBe(''); await inputs[0].setValue(''); await inputs[2].setValue('new-value'); + await inputs[3].setValue('new-value'); + await inputs[4].setValue('new-value'); expect(await inputs[0].getValue()).toBe(''); expect(await inputs[2].getValue()).toBe('new-value'); + expect(await inputs[3].getValue()).toBe('new-value'); + expect(await inputs[4].getValue()).toBe('new-value'); }); it('should be able to get disabled state', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].isDisabled()).toBe(false); expect(await inputs[1].isDisabled()).toBe(false); expect(await inputs[2].isDisabled()).toBe(false); + expect(await inputs[3].isDisabled()).toBe(false); + expect(await inputs[4].isDisabled()).toBe(false); fixture.componentInstance.disabled = true; @@ -99,11 +113,13 @@ export function runHarnessTests( it('should be able to get readonly state', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].isReadonly()).toBe(false); expect(await inputs[1].isReadonly()).toBe(false); expect(await inputs[2].isReadonly()).toBe(false); + expect(await inputs[3].isReadonly()).toBe(false); + expect(await inputs[4].isReadonly()).toBe(false); fixture.componentInstance.readonly = true; @@ -112,11 +128,13 @@ export function runHarnessTests( it('should be able to get required state', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].isRequired()).toBe(false); expect(await inputs[1].isRequired()).toBe(false); expect(await inputs[2].isRequired()).toBe(false); + expect(await inputs[3].isRequired()).toBe(false); + expect(await inputs[4].isRequired()).toBe(false); fixture.componentInstance.required = true; @@ -125,18 +143,22 @@ export function runHarnessTests( it('should be able to get placeholder of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getPlaceholder()).toBe('Favorite food'); expect(await inputs[1].getPlaceholder()).toBe(''); expect(await inputs[2].getPlaceholder()).toBe('Leave a comment'); + expect(await inputs[3].getPlaceholder()).toBe('Native control'); + expect(await inputs[4].getPlaceholder()).toBe(''); }); it('should be able to get type of input', async () => { const inputs = await loader.getAllHarnesses(inputHarness); - expect(inputs.length).toBe(3); + expect(inputs.length).toBe(5); expect(await inputs[0].getType()).toBe('text'); expect(await inputs[1].getType()).toBe('number'); expect(await inputs[2].getType()).toBe('textarea'); + expect(await inputs[3].getType()).toBe('text'); + expect(await inputs[4].getType()).toBe('textarea'); fixture.componentInstance.inputType = 'text'; @@ -180,6 +202,24 @@ function getActiveElementTagName() { + + + + + + + + + + + + + ` }) class InputHarnessTest { diff --git a/src/material/list/_list-theme.scss b/src/material/list/_list-theme.scss index 71fe8e938724..9095273ca551 100644 --- a/src/material/list/_list-theme.scss +++ b/src/material/list/_list-theme.scss @@ -33,6 +33,12 @@ background: mat-color($background, 'hover'); } } + + .mat-list-single-selected-option { + &, &:hover, &:focus { + background: mat-color($background, hover, 0.12); + } + } } @mixin mat-list-typography($config) { diff --git a/src/material/list/list-option.html b/src/material/list/list-option.html index b785eca5a6e6..d8a34a308cbf 100644 --- a/src/material/list/list-option.html +++ b/src/material/list/list-option.html @@ -7,6 +7,7 @@ [matRippleDisabled]="_isRippleDisabled()">
    diff --git a/src/material/list/list.scss b/src/material/list/list.scss index b38b5f7d1e9e..3697e0804d0f 100644 --- a/src/material/list/list.scss +++ b/src/material/list/list.scss @@ -278,8 +278,8 @@ mat-action-list { font: inherit; outline: inherit; -webkit-tap-highlight-color: transparent; - text-align: left; + [dir='rtl'] & { text-align: right; } @@ -300,7 +300,11 @@ mat-action-list { outline: none; } -@include cdk-high-contrast { +.mat-list-item-disabled { + pointer-events: none; +} + +@include cdk-high-contrast(active, off) { .mat-selection-list:focus { outline-style: dotted; } diff --git a/src/material/list/list.spec.ts b/src/material/list/list.spec.ts index 208bdec247d8..e8ae421da7ca 100644 --- a/src/material/list/list.spec.ts +++ b/src/material/list/list.spec.ts @@ -16,7 +16,7 @@ describe('MatList', () => { ListWithOneAnchorItem, ListWithOneItem, ListWithTwoLineItem, ListWithThreeLineItem, ListWithAvatar, ListWithItemWithCssClass, ListWithDynamicNumberOfLines, ListWithMultipleItems, ListWithManyLines, NavListWithOneAnchorItem, ActionListWithoutType, - ActionListWithType, ListWithIndirectDescendantLines + ActionListWithType, ListWithIndirectDescendantLines, ListWithDisabledItems, ], }); @@ -275,8 +275,40 @@ describe('MatList', () => { expect(listItems[0].nativeElement.className).toContain('mat-2-line'); expect(listItems[1].nativeElement.className).toContain('mat-2-line'); }); -}); + it('should be able to disable a single list item', () => { + const fixture = TestBed.createComponent(ListWithDisabledItems); + const listItems: HTMLElement[] = + Array.from(fixture.nativeElement.querySelectorAll('mat-list-item')); + fixture.detectChanges(); + + expect(listItems.map(item => { + return item.classList.contains('mat-list-item-disabled'); + })).toEqual([false, false, false]); + + fixture.componentInstance.firstItemDisabled = true; + fixture.detectChanges(); + + expect(listItems.map(item => { + return item.classList.contains('mat-list-item-disabled'); + })).toEqual([true, false, false]); + }); + + it('should be able to disable the entire list', () => { + const fixture = TestBed.createComponent(ListWithDisabledItems); + const listItems: HTMLElement[] = + Array.from(fixture.nativeElement.querySelectorAll('mat-list-item')); + fixture.detectChanges(); + + expect(listItems.every(item => item.classList.contains('mat-list-item-disabled'))).toBe(false); + + fixture.componentInstance.listDisabled = true; + fixture.detectChanges(); + + expect(listItems.every(item => item.classList.contains('mat-list-item-disabled'))).toBe(true); + }); + +}); class BaseTestList { items: any[] = [ @@ -425,3 +457,15 @@ class ListWithMultipleItems extends BaseTestList { } }) class ListWithIndirectDescendantLines extends BaseTestList { } + + +@Component({template: ` + + One + Two + Three + `}) +class ListWithDisabledItems { + firstItemDisabled = false; + listDisabled = false; +} diff --git a/src/material/list/list.ts b/src/material/list/list.ts index 2be160ab501c..f51d41b4d295 100644 --- a/src/material/list/list.ts +++ b/src/material/list/list.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; +import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion'; import { AfterContentInit, ChangeDetectionStrategy, @@ -21,13 +21,17 @@ import { OnChanges, OnDestroy, ChangeDetectorRef, + Input, } from '@angular/core'; import { + CanDisable, + CanDisableCtor, CanDisableRipple, CanDisableRippleCtor, MatLine, setLines, mixinDisableRipple, + mixinDisabled, } from '@angular/material/core'; import {Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; @@ -35,8 +39,8 @@ import {takeUntil} from 'rxjs/operators'; // Boilerplate for applying mixins to MatList. /** @docs-private */ class MatListBase {} -const _MatListMixinBase: CanDisableRippleCtor & typeof MatListBase = - mixinDisableRipple(MatListBase); +const _MatListMixinBase: CanDisableRippleCtor & CanDisableCtor & typeof MatListBase = + mixinDisabled(mixinDisableRipple(MatListBase)); // Boilerplate for applying mixins to MatListItem. /** @docs-private */ @@ -53,12 +57,12 @@ const _MatListItemMixinBase: CanDisableRippleCtor & typeof MatListItemBase = }, templateUrl: 'list.html', styleUrls: ['list.css'], - inputs: ['disableRipple'], + inputs: ['disableRipple', 'disabled'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges, - OnDestroy { +export class MatNavList extends _MatListMixinBase implements CanDisable, CanDisableRipple, + OnChanges, OnDestroy { /** Emits when the state of the list changes. */ _stateChanges = new Subject(); @@ -71,6 +75,7 @@ export class MatNavList extends _MatListMixinBase implements CanDisableRipple, O } static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; } @Component({ @@ -81,11 +86,12 @@ export class MatNavList extends _MatListMixinBase implements CanDisableRipple, O 'class': 'mat-list mat-list-base' }, styleUrls: ['list.css'], - inputs: ['disableRipple'], + inputs: ['disableRipple', 'disabled'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy { +export class MatList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges, + OnDestroy { /** Emits when the state of the list changes. */ _stateChanges = new Subject(); @@ -120,6 +126,7 @@ export class MatList extends _MatListMixinBase implements CanDisableRipple, OnCh } static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; } /** @@ -158,6 +165,7 @@ export class MatListSubheaderCssMatStyler {} exportAs: 'matListItem', host: { 'class': 'mat-list-item', + '[class.mat-list-item-disabled]': 'disabled', // @breaking-change 8.0.0 Remove `mat-list-item-avatar` in favor of `mat-list-item-with-avatar`. '[class.mat-list-item-avatar]': '_avatar || _icon', '[class.mat-list-item-with-avatar]': '_avatar || _icon', @@ -202,6 +210,14 @@ export class MatListItem extends _MatListItemMixinBase implements AfterContentIn } } + /** Whether the option is disabled. */ + @Input() + get disabled() { return this._disabled || !!(this._list && this._list.disabled); } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + ngAfterContentInit() { setLines(this._lines, this._element); } @@ -223,4 +239,5 @@ export class MatListItem extends _MatListItemMixinBase implements AfterContentIn } static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; } diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts index ce45f1aa4483..ed9644a37374 100644 --- a/src/material/list/selection-list.spec.ts +++ b/src/material/list/selection-list.spec.ts @@ -188,6 +188,16 @@ describe('MatSelectionList without forms', () => { expect(selectList.selected.length).toBe(0); }); + it('should not add the mat-list-single-selected-option class (in multiple mode)', () => { + let testListItem = listOptions[2].injector.get(MatListOption); + + testListItem._handleClick(); + fixture.detectChanges(); + + expect(listOptions[2].nativeElement.classList.contains('mat-list-single-selected-option')) + .toBe(false); + }); + it('should not allow selection of disabled items', () => { let testListItem = listOptions[0].injector.get(MatListOption); let selectList = @@ -882,6 +892,80 @@ describe('MatSelectionList without forms', () => { expect(listOption.classList).toContain('mat-list-item-with-avatar'); }); }); + + describe('with single selection', () => { + let fixture: ComponentFixture; + let listOption: DebugElement[]; + let selectionList: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatListModule], + declarations: [ + SelectionListWithListOptions, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SelectionListWithListOptions); + fixture.componentInstance.multiple = false; + listOption = fixture.debugElement.queryAll(By.directive(MatListOption)); + selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; + fixture.detectChanges(); + })); + + it('should select one option at a time', () => { + const testListItem1 = listOption[1].injector.get(MatListOption); + const testListItem2 = listOption[2].injector.get(MatListOption); + const selectList = + selectionList.injector.get(MatSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + + dispatchFakeEvent(testListItem1._getHostElement(), 'click'); + fixture.detectChanges(); + + expect(selectList.selected).toEqual([testListItem1]); + expect(listOption[1].nativeElement.classList.contains('mat-list-single-selected-option')) + .toBe(true); + + dispatchFakeEvent(testListItem2._getHostElement(), 'click'); + fixture.detectChanges(); + + expect(selectList.selected).toEqual([testListItem2]); + expect(listOption[1].nativeElement.classList.contains('mat-list-single-selected-option')) + .toBe(false); + expect(listOption[2].nativeElement.classList.contains('mat-list-single-selected-option')) + .toBe(true); + }); + + it('should not show check boxes', () => { + expect(fixture.nativeElement.querySelector('mat-pseudo-checkbox')).toBeFalsy(); + }); + + it('should not deselect the target option on click', () => { + const testListItem1 = listOption[1].injector.get(MatListOption); + const selectList = + selectionList.injector.get(MatSelectionList).selectedOptions; + + expect(selectList.selected.length).toBe(0); + + dispatchFakeEvent(testListItem1._getHostElement(), 'click'); + fixture.detectChanges(); + + expect(selectList.selected).toEqual([testListItem1]); + + dispatchFakeEvent(testListItem1._getHostElement(), 'click'); + fixture.detectChanges(); + + expect(selectList.selected).toEqual([testListItem1]); + }); + + it('throws an exception when toggling single/multiple mode after bootstrap', () => { + fixture.componentInstance.multiple = true; + expect(() => fixture.detectChanges()).toThrow(new Error( + 'Cannot change `multiple` mode of mat-selection-list after initialization.')); + }); + }); }); describe('MatSelectionList with forms', () => { @@ -1255,7 +1339,8 @@ describe('MatSelectionList with forms', () => { id="selection-list-1" (selectionChange)="onValueChange($event)" [disableRipple]="listRippleDisabled" - [color]="selectionListColor"> + [color]="selectionListColor" + [multiple]="multiple"> Inbox (disabled selection-option) @@ -1274,6 +1359,7 @@ describe('MatSelectionList with forms', () => { class SelectionListWithListOptions { showLastOption: boolean = true; listRippleDisabled = false; + multiple = true; selectionListColor: ThemePalette; firstOptionColor: ThemePalette; diff --git a/src/material/list/selection-list.ts b/src/material/list/selection-list.ts index 559ec05863a1..21d04e81c275 100644 --- a/src/material/list/selection-list.ts +++ b/src/material/list/selection-list.ts @@ -40,6 +40,7 @@ import { SimpleChanges, ViewChild, ViewEncapsulation, + isDevMode, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import { @@ -50,6 +51,7 @@ import { setLines, ThemePalette, } from '@angular/material/core'; + import {Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; @@ -108,6 +110,7 @@ export class MatSelectionListChange { // be placed inside a parent that has one of the other colors with a higher specificity. '[class.mat-accent]': 'color !== "primary" && color !== "warn"', '[class.mat-warn]': 'color === "warn"', + '[class.mat-list-single-selected-option]': 'selected && !selectionList.multiple', '[attr.aria-selected]': 'selected', '[attr.aria-disabled]': 'disabled', }, @@ -255,7 +258,7 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte } _handleClick() { - if (!this.disabled) { + if (!this.disabled && (this.selectionList.multiple || !this.selected)) { this.toggle(); // Emit a change event if the selected state of the option changed through user interaction. @@ -324,7 +327,7 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte 'class': 'mat-selection-list mat-list-base', '(blur)': '_onTouched()', '(keydown)': '_keydown($event)', - 'aria-multiselectable': 'true', + '[attr.aria-multiselectable]': 'multiple', '[attr.aria-disabled]': 'disabled.toString()', }, template: '', @@ -335,6 +338,8 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte }) export class MatSelectionList extends _MatSelectionListMixinBase implements CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy, OnChanges { + private _multiple = true; + private _contentInitialized = false; /** The FocusKeyManager which handles focus. */ _keyManager: FocusKeyManager; @@ -373,8 +378,25 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD } private _disabled: boolean = false; + /** Whether selection is limited to one or multiple items (default multiple). */ + @Input() + get multiple(): boolean { return this._multiple; } + set multiple(value: boolean) { + const newValue = coerceBooleanProperty(value); + + if (newValue !== this._multiple) { + if (isDevMode() && this._contentInitialized) { + throw new Error( + 'Cannot change `multiple` mode of mat-selection-list after initialization.'); + } + + this._multiple = newValue; + this.selectedOptions = new SelectionModel(this._multiple, this.selectedOptions.selected); + } + } + /** The currently selected options. */ - selectedOptions: SelectionModel = new SelectionModel(true); + selectedOptions = new SelectionModel(this._multiple); /** View to model callback that should be called whenever the selected options change. */ private _onChange: (value: any) => void = (_: any) => {}; @@ -397,6 +419,8 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD } ngAfterContentInit(): void { + this._contentInitialized = true; + this._keyManager = new FocusKeyManager(this.options) .withWrap() .withTypeAhead() @@ -589,7 +613,7 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD if (focusedIndex != null && this._isValidIndex(focusedIndex)) { let focusedOption: MatListOption = this.options.toArray()[focusedIndex]; - if (focusedOption && !focusedOption.disabled) { + if (focusedOption && !focusedOption.disabled && (this._multiple || !focusedOption.selected)) { focusedOption.toggle(); // Emit a change event because the focused option changed its state through user @@ -642,4 +666,5 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_multiple: BooleanInput; } diff --git a/src/material/menu/menu-item.ts b/src/material/menu/menu-item.ts index 8056fca55aa8..61186387179e 100644 --- a/src/material/menu/menu-item.ts +++ b/src/material/menu/menu-item.ts @@ -36,8 +36,7 @@ const _MatMenuItemMixinBase: CanDisableRippleCtor & CanDisableCtor & typeof MatM mixinDisableRipple(mixinDisabled(MatMenuItemBase)); /** - * This directive is intended to be used inside an mat-menu tag. - * It exists mostly to set the role attribute. + * Single item inside of a `mat-menu`. Provides the menu item styling and accessibility treatment. */ @Component({ selector: '[mat-menu-item]', diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index dff3b6a2c2f8..6ee99968a766 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -66,10 +66,7 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({passive: tr // TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors -/** - * This directive is intended to be used in conjunction with an mat-menu tag. It is - * responsible for toggling the display of the provided menu instance. - */ +/** Directive applied to an element that should trigger a `mat-menu`. */ @Directive({ selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`, host: { diff --git a/src/material/menu/menu.scss b/src/material/menu/menu.scss index cf477589ad7c..afcd241fc5ee 100644 --- a/src/material/menu/menu.scss +++ b/src/material/menu/menu.scss @@ -28,7 +28,7 @@ $mat-menu-submenu-indicator-size: 10px !default; pointer-events: none; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } @@ -53,7 +53,7 @@ $mat-menu-submenu-indicator-size: 10px !default; pointer-events: none; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { &.cdk-program-focused, &.cdk-keyboard-focused, &-highlighted { diff --git a/src/material/menu/menu.ts b/src/material/menu/menu.ts index d024447bb6f3..cf79043cffee 100644 --- a/src/material/menu/menu.ts +++ b/src/material/menu/menu.ts @@ -475,6 +475,9 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel this._directDescendantItems.notifyOnChanges(); }); } + + static ngAcceptInputType_overlapTrigger: BooleanInput; + static ngAcceptInputType_hasBackdrop: BooleanInput; } /** @docs-private We show the "_MatMenu" class as "MatMenu" in the docs. */ @@ -516,7 +519,4 @@ export class _MatMenu extends MatMenu { @Inject(MAT_MENU_DEFAULT_OPTIONS) defaultOptions: MatMenuDefaultOptions) { super(elementRef, ngZone, defaultOptions); } - - static ngAcceptInputType_overlapTrigger: BooleanInput; - static ngAcceptInputType_hasBackdrop: BooleanInput; } diff --git a/src/material/paginator/paginator.spec.ts b/src/material/paginator/paginator.spec.ts index 3a7b8a51b2ee..1773ef92b23e 100644 --- a/src/material/paginator/paginator.spec.ts +++ b/src/material/paginator/paginator.spec.ts @@ -1,45 +1,31 @@ -import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing'; -import {Component, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, tick, fakeAsync} from '@angular/core/testing'; +import {Component, ViewChild, Type, Provider} from '@angular/core'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {dispatchMouseEvent} from '@angular/cdk/testing/private'; import {ThemePalette} from '@angular/material/core'; import {MatSelect} from '@angular/material/select'; import {By} from '@angular/platform-browser'; import {MatPaginatorModule, MatPaginator, MatPaginatorIntl} from './index'; +import {MAT_PAGINATOR_DEFAULT_OPTIONS, MatPaginatorDefaultOptions} from './paginator'; describe('MatPaginator', () => { - let fixture: ComponentFixture; - let component: MatPaginatorApp; - let paginator: MatPaginator; - - beforeEach(async(() => { + function createComponent(type: Type, providers: Provider[] = []): ComponentFixture { TestBed.configureTestingModule({ - imports: [ - MatPaginatorModule, - NoopAnimationsModule, - ], - declarations: [ - MatPaginatorApp, - MatPaginatorWithoutPageSizeApp, - MatPaginatorWithoutOptionsApp, - MatPaginatorWithoutInputsApp, - MatPaginatorWithStringValues - ], - providers: [MatPaginatorIntl] + imports: [MatPaginatorModule, NoopAnimationsModule], + declarations: [type], + providers: [MatPaginatorIntl, ...providers] }).compileComponents(); - })); - beforeEach(() => { - fixture = TestBed.createComponent(MatPaginatorApp); + const fixture = TestBed.createComponent(type); fixture.detectChanges(); - - component = fixture.componentInstance; - paginator = component.paginator; - }); + return fixture; + } describe('with the default internationalization provider', () => { it('should show the right range text', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; const rangeElement = fixture.nativeElement.querySelector('.mat-paginator-range-label'); // View second page of list of 100, each page contains 10 items. @@ -86,6 +72,7 @@ describe('MatPaginator', () => { }); it('should show right aria-labels for select and buttons', () => { + const fixture = createComponent(MatPaginatorApp); const select = fixture.nativeElement.querySelector('.mat-select'); expect(select.getAttribute('aria-label')).toBe('Items per page:'); @@ -93,20 +80,24 @@ describe('MatPaginator', () => { expect(getNextButton(fixture).getAttribute('aria-label')).toBe('Next page'); }); - it('should re-render when the i18n labels change', - inject([MatPaginatorIntl], (intl: MatPaginatorIntl) => { - const label = fixture.nativeElement.querySelector('.mat-paginator-page-size-label'); + it('should re-render when the i18n labels change', () => { + const fixture = createComponent(MatPaginatorApp); + const label = fixture.nativeElement.querySelector('.mat-paginator-page-size-label'); + const intl = TestBed.get(MatPaginatorIntl); - intl.itemsPerPageLabel = '1337 items per page'; - intl.changes.next(); - fixture.detectChanges(); + intl.itemsPerPageLabel = '1337 items per page'; + intl.changes.next(); + fixture.detectChanges(); - expect(label.textContent!.trim()).toBe('1337 items per page'); - })); + expect(label.textContent!.trim()).toBe('1337 items per page'); + }); }); describe('when navigating with the next and previous buttons', () => { it('should be able to go to the next page', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; expect(paginator.pageIndex).toBe(0); dispatchMouseEvent(getNextButton(fixture), 'click'); @@ -119,6 +110,9 @@ describe('MatPaginator', () => { }); it('should be able to go to the previous page', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; paginator.pageIndex = 1; fixture.detectChanges(); expect(paginator.pageIndex).toBe(1); @@ -134,6 +128,7 @@ describe('MatPaginator', () => { }); it('should be able to show the first/last buttons', () => { + const fixture = createComponent(MatPaginatorApp); expect(getFirstButton(fixture)) .toBeNull('Expected first button to not exist.'); @@ -151,6 +146,9 @@ describe('MatPaginator', () => { }); it('should mark itself as initialized', fakeAsync(() => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; let isMarkedInitialized = false; paginator.initialized.subscribe(() => isMarkedInitialized = true); @@ -159,16 +157,24 @@ describe('MatPaginator', () => { })); it('should not allow a negative pageSize', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; paginator.pageSize = -1337; expect(paginator.pageSize).toBeGreaterThanOrEqual(0); }); it('should not allow a negative pageIndex', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; paginator.pageIndex = -42; expect(paginator.pageIndex).toBeGreaterThanOrEqual(0); }); it('should be able to set the color of the form field', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; const formField: HTMLElement = fixture.nativeElement.querySelector('.mat-form-field'); expect(formField.classList).toContain('mat-primary'); @@ -181,8 +187,14 @@ describe('MatPaginator', () => { }); describe('when showing the first and last button', () => { + let fixture: ComponentFixture; + let component: MatPaginatorApp; + let paginator: MatPaginator; beforeEach(() => { + fixture = createComponent(MatPaginatorApp); + component = fixture.componentInstance; + paginator = component.paginator; component.showFirstLastButtons = true; fixture.detectChanges(); }); @@ -245,6 +257,9 @@ describe('MatPaginator', () => { }); it('should mark for check when inputs are changed directly', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; const rangeElement = fixture.nativeElement.querySelector('.mat-paginator-range-label'); expect(rangeElement.innerText.trim()).toBe('1 – 10 of 100'); @@ -270,21 +285,23 @@ describe('MatPaginator', () => { }); it('should default the page size options to the page size if no options provided', () => { - const withoutOptionsAppFixture = TestBed.createComponent(MatPaginatorWithoutOptionsApp); - withoutOptionsAppFixture.detectChanges(); + const fixture = createComponent(MatPaginatorWithoutOptionsApp); + fixture.detectChanges(); - expect(withoutOptionsAppFixture.componentInstance.paginator._displayedPageSizeOptions) - .toEqual([10]); + expect(fixture.componentInstance.paginator._displayedPageSizeOptions).toEqual([10]); }); it('should default the page size to the first page size option if not provided', () => { - const withoutPageSizeAppFixture = TestBed.createComponent(MatPaginatorWithoutPageSizeApp); - withoutPageSizeAppFixture.detectChanges(); + const fixture = createComponent(MatPaginatorWithoutPageSizeApp); + fixture.detectChanges(); - expect(withoutPageSizeAppFixture.componentInstance.paginator.pageSize).toEqual(10); + expect(fixture.componentInstance.paginator.pageSize).toEqual(10); }); it('should show a sorted list of page size options including the current page size', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]); component.pageSize = 30; @@ -298,6 +315,10 @@ describe('MatPaginator', () => { }); it('should be able to change the page size while keeping the first item present', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; + // Start on the third page of a list of 100 with a page size of 10. component.pageIndex = 4; component.pageSize = 10; @@ -339,6 +360,10 @@ describe('MatPaginator', () => { }); it('should keep track of the right number of pages', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; + component.pageSize = 10; component.length = 100; fixture.detectChanges(); @@ -356,6 +381,10 @@ describe('MatPaginator', () => { }); it('should show a select only if there are multiple options', () => { + const fixture = createComponent(MatPaginatorApp); + const component = fixture.componentInstance; + const paginator = component.paginator; + expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]); expect(fixture.nativeElement.querySelector('.mat-select')).not.toBeNull(); @@ -367,10 +396,10 @@ describe('MatPaginator', () => { }); it('should handle the number inputs being passed in as strings', () => { - const withStringFixture = TestBed.createComponent(MatPaginatorWithStringValues); - withStringFixture.detectChanges(); + const fixture = createComponent(MatPaginatorWithStringValues); + fixture.detectChanges(); - const withStringPaginator = withStringFixture.componentInstance.paginator; + const withStringPaginator = fixture.componentInstance.paginator; expect(withStringPaginator.pageIndex).toEqual(0); expect(withStringPaginator.length).toEqual(100); expect(withStringPaginator.pageSize).toEqual(10); @@ -378,6 +407,7 @@ describe('MatPaginator', () => { }); it('should be able to hide the page size select', () => { + const fixture = createComponent(MatPaginatorApp); const element = fixture.nativeElement; expect(element.querySelector('.mat-paginator-page-size')) @@ -391,6 +421,7 @@ describe('MatPaginator', () => { }); it('should be able to disable all the controls in the paginator via the binding', () => { + const fixture = createComponent(MatPaginatorApp); const select: MatSelect = fixture.debugElement.query(By.directive(MatSelect))!.componentInstance; @@ -414,6 +445,25 @@ describe('MatPaginator', () => { expect(getLastButton(fixture).hasAttribute('disabled')).toBe(true); }); + + it('should be able to configure the default options via a provider', () => { + const fixture = createComponent(MatPaginatorWithoutInputsApp, [{ + provide: MAT_PAGINATOR_DEFAULT_OPTIONS, + useValue: { + pageSize: 7, + pageSizeOptions: [7, 14, 21], + hidePageSize: true, + showFirstLastButtons: true + } as MatPaginatorDefaultOptions + }]); + const paginator = fixture.componentInstance.paginator; + + expect(paginator.pageSize).toBe(7); + expect(paginator.pageSizeOptions).toEqual([7, 14, 21]); + expect(paginator.hidePageSize).toBe(true); + expect(paginator.showFirstLastButtons).toBe(true); + }); + }); function getPreviousButton(fixture: ComponentFixture) { diff --git a/src/material/paginator/paginator.ts b/src/material/paginator/paginator.ts index 3e1c34c9d82e..e014487ae9a7 100644 --- a/src/material/paginator/paginator.ts +++ b/src/material/paginator/paginator.ts @@ -22,6 +22,9 @@ import { OnInit, Output, ViewEncapsulation, + InjectionToken, + Inject, + Optional, } from '@angular/core'; import {Subscription} from 'rxjs'; import {MatPaginatorIntl} from './paginator-intl'; @@ -59,6 +62,26 @@ export class PageEvent { length: number; } + +/** Object that can be used to configure the default options for the paginator module. */ +export interface MatPaginatorDefaultOptions { + /** Number of items to display on a page. By default set to 50. */ + pageSize?: number; + + /** The set of provided page size options to display to the user. */ + pageSizeOptions?: number[]; + + /** Whether to hide the page size selection UI from the user. */ + hidePageSize?: boolean; + + /** Whether to show the first/last buttons UI to the user. */ + showFirstLastButtons?: boolean; +} + +/** Injection token that can be used to provide the default options for the paginator module. */ +export const MAT_PAGINATOR_DEFAULT_OPTIONS = + new InjectionToken('MAT_PAGINATOR_DEFAULT_OPTIONS'); + // Boilerplate for applying mixins to MatPaginator. /** @docs-private */ class MatPaginatorBase {} @@ -150,9 +173,31 @@ export class MatPaginator extends _MatPaginatorBase implements OnInit, OnDestroy _displayedPageSizeOptions: number[]; constructor(public _intl: MatPaginatorIntl, - private _changeDetectorRef: ChangeDetectorRef) { + private _changeDetectorRef: ChangeDetectorRef, + @Optional() @Inject(MAT_PAGINATOR_DEFAULT_OPTIONS) + defaults?: MatPaginatorDefaultOptions) { super(); this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck()); + + if (defaults) { + const {pageSize, pageSizeOptions, hidePageSize, showFirstLastButtons} = defaults; + + if (pageSize != null) { + this._pageSize = pageSize; + } + + if (pageSizeOptions != null) { + this._pageSizeOptions = pageSizeOptions; + } + + if (hidePageSize != null) { + this._hidePageSize = hidePageSize; + } + + if (showFirstLastButtons != null) { + this._showFirstLastButtons = showFirstLastButtons; + } + } } ngOnInit() { diff --git a/src/material/progress-bar/progress-bar.scss b/src/material/progress-bar/progress-bar.scss index 55be72a7beb9..0e3670aedaeb 100644 --- a/src/material/progress-bar/progress-bar.scss +++ b/src/material/progress-bar/progress-bar.scss @@ -32,7 +32,7 @@ $mat-progress-bar-piece-animation-duration: 250ms !default; // during the background scroll animation. width: calc(100% + 10px); - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { display: none; } } @@ -43,7 +43,7 @@ $mat-progress-bar-piece-animation-duration: 250ms !default; transform-origin: top left; transition: transform $mat-progress-bar-piece-animation-duration ease; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-top: solid 5px; opacity: 0.5; } @@ -61,7 +61,7 @@ $mat-progress-bar-piece-animation-duration: 250ms !default; transform-origin: top left; transition: transform $mat-progress-bar-piece-animation-duration ease; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-top: solid $mat-progress-bar-height; } } diff --git a/src/material/progress-spinner/progress-spinner.ts b/src/material/progress-spinner/progress-spinner.ts index 0bcf5dce5551..2bff6ed06786 100644 --- a/src/material/progress-spinner/progress-spinner.ts +++ b/src/material/progress-spinner/progress-spinner.ts @@ -332,10 +332,6 @@ export class MatSpinner extends MatProgressSpinner { super(elementRef, platform, document, animationMode, defaults); this.mode = 'indeterminate'; } - - static ngAcceptInputType_diameter: NumberInput; - static ngAcceptInputType_strokeWidth: NumberInput; - static ngAcceptInputType_value: NumberInput; } diff --git a/src/material/radio/BUILD.bazel b/src/material/radio/BUILD.bazel index b66b49a8f8f3..b6f7bdac23eb 100644 --- a/src/material/radio/BUILD.bazel +++ b/src/material/radio/BUILD.bazel @@ -26,7 +26,6 @@ ng_module( "//src/cdk/collections", "//src/material/core", "@npm//@angular/animations", - "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/forms", "@npm//@angular/platform-browser", diff --git a/src/material/radio/radio-module.ts b/src/material/radio/radio-module.ts index 35e796419e01..eaa23c964615 100644 --- a/src/material/radio/radio-module.ts +++ b/src/material/radio/radio-module.ts @@ -6,14 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatRippleModule} from '@angular/material/core'; import {MatRadioButton, MatRadioGroup} from './radio'; @NgModule({ - imports: [CommonModule, MatRippleModule, MatCommonModule], + imports: [MatRippleModule, MatCommonModule], exports: [MatRadioGroup, MatRadioButton, MatCommonModule], declarations: [MatRadioGroup, MatRadioButton], }) diff --git a/src/material/radio/radio.scss b/src/material/radio/radio.scss index 3c23db135511..da6b0a088d7d 100644 --- a/src/material/radio/radio.scss +++ b/src/material/radio/radio.scss @@ -85,7 +85,7 @@ $mat-radio-ripple-radius: 20px; .mat-radio-checked & { transform: scale(0.5); - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { // Since we use a background color to render the circle, it won't be // displayed in high contrast mode. Use a border as a fallback. border: solid $mat-radio-size / 2; @@ -183,7 +183,7 @@ $mat-radio-ripple-radius: 20px; left: 50%; } -@include cdk-high-contrast { +@include cdk-high-contrast(active, off) { .mat-radio-disabled { opacity: 0.5; } diff --git a/src/material/schematics/BUILD.bazel b/src/material/schematics/BUILD.bazel index 2655d84e1e27..f7a210f4ea4f 100644 --- a/src/material/schematics/BUILD.bazel +++ b/src/material/schematics/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "npm_package") +load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") load("//:packages.bzl", "VERSION_PLACEHOLDER_REPLACEMENTS") load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") @@ -38,10 +38,10 @@ ts_library( ) # This package is intended to be combined into the main @angular/material package as a dep. -npm_package( +pkg_npm( name = "npm_package", srcs = [":schematics_assets"], - replacements = VERSION_PLACEHOLDER_REPLACEMENTS, + substitutions = VERSION_PLACEHOLDER_REPLACEMENTS, deps = [":schematics"], ) diff --git a/src/material/schematics/ng-add/index.spec.ts b/src/material/schematics/ng-add/index.spec.ts index aaafcf89be33..3c1422e4528b 100644 --- a/src/material/schematics/ng-add/index.spec.ts +++ b/src/material/schematics/ng-add/index.spec.ts @@ -57,8 +57,8 @@ describe('ng-add schematic', () => { const dependencies = packageJson.dependencies; const angularCoreVersion = dependencies['@angular/core']; - expect(dependencies['@angular/material']).toBeDefined(); - expect(dependencies['@angular/cdk']).toBeDefined(); + expect(dependencies['@angular/material']).toBe('~0.0.0-PLACEHOLDER'); + expect(dependencies['@angular/cdk']).toBe('~0.0.0-PLACEHOLDER'); expect(dependencies['@angular/forms']) .toBe( angularCoreVersion, @@ -73,7 +73,10 @@ describe('ng-add schematic', () => { Object.keys(dependencies).sort(), 'Expected the modified "dependencies" to be sorted alphabetically.'); - expect(runner.tasks.some(task => task.name === 'run-schematic')).toBe(true); + expect(runner.tasks.some(task => task.name === 'node-package')).toBe(true, + 'Expected the package manager to be scheduled in order to update lock files.'); + expect(runner.tasks.some(task => task.name === 'run-schematic')).toBe(true, + 'Expected the setup-project schematic to be scheduled.'); }); it('should add default theme', async () => { @@ -384,4 +387,27 @@ describe('ng-add schematic', () => { expect(buffer.toString()).toContain(''); }); }); + + it('should not add the global typography class if the user did not opt into it', async () => { + appTree.overwrite('projects/material/src/index.html', ` + + + + + `); + + const tree = await runner.runSchematicAsync('ng-add-setup-project', { + typography: false + }, appTree).toPromise(); + + const workspace = getWorkspace(tree); + const project = getProjectFromWorkspace(workspace); + const indexFiles = getProjectIndexFiles(project); + expect(indexFiles.length).toBe(1); + + indexFiles.forEach(indexPath => { + const buffer = tree.read(indexPath)!; + expect(buffer.toString()).toContain(''); + }); + }); }); diff --git a/src/material/schematics/ng-add/index.ts b/src/material/schematics/ng-add/index.ts index 8c033839fae4..076cb39841a9 100644 --- a/src/material/schematics/ng-add/index.ts +++ b/src/material/schematics/ng-add/index.ts @@ -27,8 +27,9 @@ export default function(options: Schema): Rule { const ngCoreVersionTag = getPackageVersionFromPackageJson(host, '@angular/core'); const angularDependencyVersion = ngCoreVersionTag || requiredAngularVersionRange; - // In order to align the Material and CDK version with the other Angular dependencies, - // we use tilde instead of caret. This is default for Angular dependencies in new CLI projects. + // In order to align the Material and CDK version with other Angular dependencies that + // are setup by "@schematics/angular", we use tilde instead of caret. This is default for + // Angular dependencies in new CLI projects. addPackageToPackageJson(host, '@angular/cdk', `~${materialVersion}`); addPackageToPackageJson(host, '@angular/material', `~${materialVersion}`); addPackageToPackageJson(host, '@angular/forms', angularDependencyVersion); diff --git a/src/material/schematics/ng-add/setup-project.ts b/src/material/schematics/ng-add/setup-project.ts index a7fda9037720..1ce7a29ab957 100644 --- a/src/material/schematics/ng-add/setup-project.ts +++ b/src/material/schematics/ng-add/setup-project.ts @@ -9,13 +9,13 @@ import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import { addModuleImportToRootModule, + getAppModulePath, getProjectFromWorkspace, getProjectMainFile, getProjectStyleFile, hasNgModuleImport, } from '@angular/cdk/schematics'; import {getWorkspace} from '@schematics/angular/utility/config'; -import {getAppModulePath} from '@schematics/angular/utility/ng-ast-utils'; import {addFontsToIndex} from './fonts/material-fonts'; import {Schema} from './schema'; import {addThemeToAppStyles, addTypographyClass} from './theming/theming'; diff --git a/src/material/schematics/ng-add/theming/theming.ts b/src/material/schematics/ng-add/theming/theming.ts index 45968ca6f642..37328b452522 100644 --- a/src/material/schematics/ng-add/theming/theming.ts +++ b/src/material/schematics/ng-add/theming/theming.ts @@ -57,7 +57,9 @@ export function addTypographyClass(options: Schema): (host: Tree) => Tree { throw new SchematicsException('No project index HTML file could be found.'); } - projectIndexFiles.forEach(indexFilePath => addBodyClass(host, indexFilePath, 'mat-typography')); + if (options.typography) { + projectIndexFiles.forEach(path => addBodyClass(host, path, 'mat-typography')); + } return host; }; diff --git a/src/material/schematics/ng-add/version-names.ts b/src/material/schematics/ng-add/version-names.ts index 4c34cf83c1f7..db02cc916ea0 100644 --- a/src/material/schematics/ng-add/version-names.ts +++ b/src/material/schematics/ng-add/version-names.ts @@ -7,21 +7,10 @@ */ /** Name of the Material version that is shipped together with the schematics. */ -export const materialVersion = - loadPackageVersionGracefully('@angular/cdk') || - loadPackageVersionGracefully('@angular/material'); +export const materialVersion = '0.0.0-PLACEHOLDER'; /** * Range of Angular versions that can be used together with the Angular Material version * that provides these schematics. */ export const requiredAngularVersionRange = '0.0.0-NG'; - -/** Loads the full version from the given Angular package gracefully. */ -function loadPackageVersionGracefully(packageName: string): string | null { - try { - return require(`${packageName}/package.json`).version; - } catch { - return null; - } -} diff --git a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts index 53d2f1599f64..90d1612362b4 100644 --- a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts +++ b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts @@ -22,12 +22,10 @@ import { PostMigrationAction, ResolvedResource, TargetVersion, -} from '@angular/cdk/schematics'; -import { addSymbolToNgModuleMetadata, getDecoratorMetadata, getMetadataField -} from '@schematics/angular/utility/ast-utils'; +} from '@angular/cdk/schematics'; import {InsertChange} from '@schematics/angular/utility/change'; import {getWorkspace} from '@schematics/angular/utility/config'; import {WorkspaceProject} from '@schematics/angular/utility/workspace-models'; diff --git a/src/material/select/select.scss b/src/material/select/select.scss index ef021f3cac73..efc5e375eb9d 100644 --- a/src/material/select/select.scss +++ b/src/material/select/select.scss @@ -97,7 +97,7 @@ $mat-select-placeholder-arrow-space: 2 * ($mat-select-arrow-size + $mat-select-a min-width: 100%; // prevents some animation twitching and test inconsistencies in IE11 border-radius: 4px; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index 63e752ad5d0d..5e289b2fe350 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -29,6 +29,7 @@ import { QueryList, ViewChild, ViewChildren, + Provider, } from '@angular/core'; import { async, @@ -63,7 +64,7 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; import {Subject, Subscription, EMPTY, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {MatSelectModule} from './index'; -import {MatSelect} from './select'; +import {MatSelect, MAT_SELECT_CONFIG, MatSelectConfig} from './select'; import { getMatSelectDynamicMultipleError, getMatSelectNonArrayValueError, @@ -88,7 +89,7 @@ describe('MatSelect', () => { * overall test time. * @param declarations Components to declare for this block */ - function configureMatSelectTestingModule(declarations: any[]) { + function configureMatSelectTestingModule(declarations: any[], providers: Provider[] = []) { TestBed.configureTestingModule({ imports: [ MatFormFieldModule, @@ -105,6 +106,7 @@ describe('MatSelect', () => { scrolled: () => scrolledSubject.asObservable(), }), }, + ...providers ], }).compileComponents(); @@ -4447,6 +4449,22 @@ describe('MatSelect', () => { })); }); + + it('should be able to provide default values through an injection token', () => { + configureMatSelectTestingModule([NgModelSelect], [{ + provide: MAT_SELECT_CONFIG, + useValue: { + disableOptionCentering: true, + typeaheadDebounceInterval: 1337 + } as MatSelectConfig + }]); + const fixture = TestBed.createComponent(NgModelSelect); + fixture.detectChanges(); + const select = fixture.componentInstance.select; + + expect(select.disableOptionCentering).toBe(true); + expect(select.typeaheadDebounceInterval).toBe(1337); + }); }); diff --git a/src/material/select/select.ts b/src/material/select/select.ts index 183d6542cb0d..f81fc4aa216d 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -151,6 +151,18 @@ export function MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay): return () => overlay.scrollStrategies.reposition(); } +/** Object that can be used to configure the default options for the select module. */ +export interface MatSelectConfig { + /** Whether option centering should be disabled. */ + disableOptionCentering?: boolean; + + /** Time to wait in milliseconds after the last keystroke before moving focus to an item. */ + typeaheadDebounceInterval?: number; +} + +/** Injection token that can be used to provide the default options the select module. */ +export const MAT_SELECT_CONFIG = new InjectionToken('MAT_SELECT_CONFIG'); + /** @docs-private */ export const MAT_SELECT_SCROLL_STRATEGY_PROVIDER = { provide: MAT_SELECT_SCROLL_STRATEGY, @@ -507,7 +519,8 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, @Self() @Optional() public ngControl: NgControl, @Attribute('tabindex') tabIndex: string, @Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any, - private _liveAnnouncer: LiveAnnouncer) { + private _liveAnnouncer: LiveAnnouncer, + @Optional() @Inject(MAT_SELECT_CONFIG) defaults?: MatSelectConfig) { super(elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl); @@ -523,6 +536,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, // Force setter to be called in case id was not specified. this.id = this.id; + + if (defaults) { + if (defaults.disableOptionCentering != null) { + this.disableOptionCentering = defaults.disableOptionCentering; + } + + if (defaults.typeaheadDebounceInterval != null) { + this.typeaheadDebounceInterval = defaults.typeaheadDebounceInterval; + } + } } ngOnInit() { @@ -1068,7 +1091,11 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, /** Gets the index of the provided option in the option list. */ private _getOptionIndex(option: MatOption): number | undefined { return this.options.reduce((result: number | undefined, current: MatOption, index: number) => { - return result === undefined ? (option === current ? index : undefined) : result; + if (result !== undefined) { + return result; + } + + return option === current ? index : undefined; }, undefined); } diff --git a/src/material/select/testing/shared.spec.ts b/src/material/select/testing/shared.spec.ts index f87405b2ea73..bf8e1573cddf 100644 --- a/src/material/select/testing/shared.spec.ts +++ b/src/material/select/testing/shared.spec.ts @@ -226,13 +226,11 @@ function getActiveElementId() { {{ state.name }} - {{ state.name }} - @@ -242,7 +240,6 @@ function getActiveElementId() { - {{ state.name }} @@ -283,4 +280,3 @@ class SelectHarnessTest { } ]; } - diff --git a/src/material/sidenav/drawer.scss b/src/material/sidenav/drawer.scss index 5fbc960ca805..97f859122245 100644 --- a/src/material/sidenav/drawer.scss +++ b/src/material/sidenav/drawer.scss @@ -86,7 +86,7 @@ $mat-drawer-over-drawer-z-index: 4; } } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; } } @@ -123,13 +123,13 @@ $mat-drawer-over-drawer-z-index: 4; transform: translate3d(-100%, 0, 0); &, [dir='rtl'] &.mat-drawer-end { - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-right: $high-contrast-border; } } [dir='rtl'] &, &.mat-drawer-end { - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border-left: $high-contrast-border; border-right: none; } diff --git a/src/material/sidenav/drawer.spec.ts b/src/material/sidenav/drawer.spec.ts index d65226cf1194..8d6e83ecb5a4 100644 --- a/src/material/sidenav/drawer.spec.ts +++ b/src/material/sidenav/drawer.spec.ts @@ -518,7 +518,7 @@ describe('MatDrawer', () => { expect(document.activeElement).toBe(firstFocusableElement); })); - it('should not trap focus when opened in "side" mode', fakeAsync(() => { + it('should not auto-focus by default when opened in "side" mode', fakeAsync(() => { testComponent.mode = 'side'; fixture.detectChanges(); lastFocusableElement.focus(); @@ -530,6 +530,19 @@ describe('MatDrawer', () => { expect(document.activeElement).toBe(lastFocusableElement); })); + it('should auto-focus when opened in "side" mode when enabled explicitly', fakeAsync(() => { + drawer.autoFocus = true; + testComponent.mode = 'side'; + fixture.detectChanges(); + lastFocusableElement.focus(); + + drawer.open(); + fixture.detectChanges(); + tick(); + + expect(document.activeElement).toBe(firstFocusableElement); + })); + it('should focus the drawer if there are no focusable elements', fakeAsync(() => { fixture.destroy(); @@ -545,7 +558,7 @@ describe('MatDrawer', () => { })); it('should be able to disable auto focus', fakeAsync(() => { - testComponent.autoFocus = false; + drawer.autoFocus = false; testComponent.mode = 'push'; fixture.detectChanges(); lastFocusableElement.focus(); @@ -981,7 +994,7 @@ class DrawerDynamicPosition { // to be focusable across all platforms. template: ` - + @@ -989,7 +1002,6 @@ class DrawerDynamicPosition { }) class DrawerWithFocusableElements { mode: string = 'over'; - autoFocus = true; } @Component({ diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index b5f113d893c9..934483a101ab 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -173,11 +173,22 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr set disableClose(value: boolean) { this._disableClose = coerceBooleanProperty(value); } private _disableClose: boolean = false; - /** Whether the drawer should focus the first focusable element automatically when opened. */ + /** + * Whether the drawer should focus the first focusable element automatically when opened. + * Defaults to false in when `mode` is set to `side`, otherwise defaults to `true`. If explicitly + * enabled, focus will be moved into the sidenav in `side` mode as well. + */ @Input() - get autoFocus(): boolean { return this._autoFocus; } + get autoFocus(): boolean { + const value = this._autoFocus; + + // Note that usually we disable auto focusing in `side` mode, because we don't know how the + // sidenav is being used, but in some cases it still makes sense to do it. If the consumer + // explicitly enabled `autoFocus`, we take it as them always wanting to enable it. + return value == null ? this.mode !== 'side' : value; + } set autoFocus(value: boolean) { this._autoFocus = coerceBooleanProperty(value); } - private _autoFocus: boolean = true; + private _autoFocus: boolean | undefined; /** * Whether the drawer is opened. We overload this because we trigger an event when it @@ -253,11 +264,6 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr */ readonly _modeChanged = new Subject(); - get _isFocusTrapEnabled(): boolean { - // The focus trap is only enabled when the drawer is open in any mode other than side. - return this.opened && this.mode !== 'side'; - } - constructor(private _elementRef: ElementRef, private _focusTrapFactory: FocusTrapFactory, private _focusMonitor: FocusMonitor, @@ -276,9 +282,7 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement; } - if (this._isFocusTrapEnabled && this._focusTrap) { - this._trapFocus(); - } + this._takeFocus(); } else { this._restoreFocus(); } @@ -316,9 +320,12 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr }); } - /** Traps focus inside the drawer. */ - private _trapFocus() { - if (!this.autoFocus) { + /** + * Moves focus into the drawer. Note that this works even if + * the focus trap is disabled in `side` mode. + */ + private _takeFocus() { + if (!this.autoFocus || !this._focusTrap) { return; } @@ -428,7 +435,8 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr /** Updates the enabled state of the focus trap. */ private _updateFocusTrapState() { if (this._focusTrap) { - this._focusTrap.enabled = this._isFocusTrapEnabled; + // The focus trap is only enabled when the drawer is open in any mode other than side. + this._focusTrap.enabled = this.opened && this.mode !== 'side'; } } diff --git a/src/material/sidenav/sidenav.ts b/src/material/sidenav/sidenav.ts index 9432102162a0..1461f832429c 100644 --- a/src/material/sidenav/sidenav.ts +++ b/src/material/sidenav/sidenav.ts @@ -104,9 +104,6 @@ export class MatSidenav extends MatDrawer { static ngAcceptInputType_fixedInViewport: BooleanInput; static ngAcceptInputType_fixedTopGap: NumberInput; static ngAcceptInputType_fixedBottomGap: NumberInput; - static ngAcceptInputType_disableClose: BooleanInput; - static ngAcceptInputType_autoFocus: BooleanInput; - static ngAcceptInputType_opened: BooleanInput; } @@ -136,7 +133,5 @@ export class MatSidenavContainer extends MatDrawerContainer { _allDrawers: QueryList; @ContentChild(MatSidenavContent) _content: MatSidenavContent; - - static ngAcceptInputType_autosize: BooleanInput; static ngAcceptInputType_hasBackdrop: BooleanInput; } diff --git a/src/material/slide-toggle/BUILD.bazel b/src/material/slide-toggle/BUILD.bazel index 8ba0c4b72e9c..88cf7b2463ce 100644 --- a/src/material/slide-toggle/BUILD.bazel +++ b/src/material/slide-toggle/BUILD.bazel @@ -56,6 +56,7 @@ ng_test_library( ), deps = [ ":slide-toggle", + "//src/cdk/a11y", "//src/cdk/bidi", "//src/cdk/observers", "//src/cdk/testing/private", diff --git a/src/material/slide-toggle/slide-toggle.scss b/src/material/slide-toggle/slide-toggle.scss index 31b24d114676..70c673a64003 100644 --- a/src/material/slide-toggle/slide-toggle.scss +++ b/src/material/slide-toggle/slide-toggle.scss @@ -217,34 +217,17 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg } /** Custom styling to make the slide-toggle usable in high contrast mode. */ -@include cdk-high-contrast() { - .mat-slide-toggle-thumb { - background: #fff; - border: 1px solid #000; - - .mat-slide-toggle.mat-checked & { - background: #000; - border: 1px solid #fff; - } - } - +@include cdk-high-contrast(active, off) { + .mat-slide-toggle-thumb, .mat-slide-toggle-bar { - background: #fff; - - // 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 & { - outline: 1px dotted; - outline-offset: ($mat-slide-toggle-height - $mat-slide-toggle-bar-height) / 2; - } + border: 1px solid; } -} -// Since the bar with a white background will be placed on a white background, we need to a black -// border in order to make sure that the bar is visible. -@include cdk-high-contrast(black-on-white) { - .mat-slide-toggle-bar { - border: 1px solid #000; + // 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: ($mat-slide-toggle-height - $mat-slide-toggle-bar-height) / 2; } } diff --git a/src/material/slide-toggle/slide-toggle.spec.ts b/src/material/slide-toggle/slide-toggle.spec.ts index ed97ce8ab4ec..f20ef066c898 100644 --- a/src/material/slide-toggle/slide-toggle.spec.ts +++ b/src/material/slide-toggle/slide-toggle.spec.ts @@ -9,9 +9,11 @@ import { flushMicrotasks, TestBed, tick, + inject, } from '@angular/core/testing'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; +import {FocusMonitor} from '@angular/cdk/a11y'; import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index'; import {MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS} from './slide-toggle-config'; @@ -310,6 +312,19 @@ describe('MatSlideToggle without forms', () => { expect(document.activeElement).toBe(inputElement); }); + it('should not manually move focus to underlying input when focus comes from mouse or touch', + inject([FocusMonitor], (focusMonitor: FocusMonitor) => { + expect(document.activeElement).not.toBe(inputElement); + + focusMonitor.focusVia(slideToggleElement, 'mouse'); + fixture.detectChanges(); + expect(document.activeElement).not.toBe(inputElement); + + focusMonitor.focusVia(slideToggleElement, 'touch'); + fixture.detectChanges(); + expect(document.activeElement).not.toBe(inputElement); + })); + it('should set a element class if labelPosition is set to before', () => { expect(slideToggleElement.classList).not.toContain('mat-slide-toggle-label-before'); diff --git a/src/material/slide-toggle/slide-toggle.ts b/src/material/slide-toggle/slide-toggle.ts index bdd9a923ce70..79aafb166f14 100644 --- a/src/material/slide-toggle/slide-toggle.ts +++ b/src/material/slide-toggle/slide-toggle.ts @@ -91,7 +91,6 @@ const _MatSlideToggleMixinBase: '[class.mat-disabled]': 'disabled', '[class.mat-slide-toggle-label-before]': 'labelPosition == "before"', '[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"', - '(focus)': '_inputElement.nativeElement.focus()', }, templateUrl: 'slide-toggle.html', styleUrls: ['slide-toggle.css'], @@ -193,7 +192,13 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro this._focusMonitor .monitor(this._elementRef, true) .subscribe(focusOrigin => { - if (!focusOrigin) { + // Only forward focus manually when it was received programmatically or through the + // keyboard. We should not do this for mouse/touch focus for two reasons: + // 1. It can prevent clicks from landing in Chrome (see #18269). + // 2. They're already handled by the wrapping `label` element. + if (focusOrigin === 'keyboard' || focusOrigin === 'program') { + this._inputElement.nativeElement.focus(); + } else if (!focusOrigin) { // When a focused element becomes disabled, the browser *immediately* fires a blur event. // Angular does not expect events to be raised during change detection, so any state // change (such as a form control's 'ng-touched') will cause a changed-after-checked diff --git a/src/material/slider/slider.scss b/src/material/slider/slider.scss index dd7c2e55af94..24f3953ddb2e 100644 --- a/src/material/slider/slider.scss +++ b/src/material/slider/slider.scss @@ -143,7 +143,7 @@ $mat-slider-focus-ring-size: 30px !default; border-radius $swift-ease-out-duration $swift-ease-out-timing-function, background-color $swift-ease-out-duration $swift-ease-out-timing-function; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } @@ -303,7 +303,7 @@ $mat-slider-focus-ring-size: 30px !default; height: $mat-slider-track-thickness; width: 100%; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { height: 0; outline: solid $mat-slider-track-thickness; top: $mat-slider-track-thickness / 2; @@ -343,7 +343,7 @@ $mat-slider-focus-ring-size: 30px !default; transform: rotate(45deg); } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { .mat-slider-thumb-label, .mat-slider-thumb-label-text { transform: none; @@ -393,7 +393,7 @@ $mat-slider-focus-ring-size: 30px !default; width: $mat-slider-track-thickness; height: 100%; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { width: 0; outline: solid $mat-slider-track-thickness; left: $mat-slider-track-thickness / 2; diff --git a/src/material/slider/slider.ts b/src/material/slider/slider.ts index 248984edb2c6..40ac91ac51eb 100644 --- a/src/material/slider/slider.ts +++ b/src/material/slider/slider.ts @@ -57,6 +57,7 @@ import { } from '@angular/material/core'; import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; import {normalizePassiveListenerOptions} from '@angular/cdk/platform'; +import {DOCUMENT} from '@angular/common'; import {Subscription} from 'rxjs'; const activeEventOptions = normalizePassiveListenerOptions({passive: false}); @@ -423,9 +424,14 @@ export class MatSlider extends _MatSliderMixinBase }; if (this._isMinValue && this._thumbGap) { - let side = this.vertical ? - (this._invertAxis ? 'Bottom' : 'Top') : - (this._invertAxis ? 'Right' : 'Left'); + let side: string; + + if (this.vertical) { + side = this._invertAxis ? 'Bottom' : 'Top'; + } else { + side = this._invertAxis ? 'Right' : 'Left'; + } + styles[`padding${side}`] = `${this._thumbGap}px`; } @@ -483,6 +489,9 @@ export class MatSlider extends _MatSliderMixinBase /** Keeps track of the last pointer event that was captured by the slider. */ private _lastPointerEvent: MouseEvent | TouchEvent | null; + /** Used to subscribe to global move and end events */ + protected _document?: Document; + constructor(elementRef: ElementRef, private _focusMonitor: FocusMonitor, private _changeDetectorRef: ChangeDetectorRef, @@ -491,9 +500,13 @@ export class MatSlider extends _MatSliderMixinBase // @breaking-change 8.0.0 `_animationMode` parameter to be made required. @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string, // @breaking-change 9.0.0 `_ngZone` parameter to be made required. - private _ngZone?: NgZone) { + private _ngZone?: NgZone, + /** @breaking-change 11.0.0 make document required */ + @Optional() @Inject(DOCUMENT) document?: any) { super(elementRef); + this._document = document; + this.tabIndex = parseInt(tabIndex) || 0; this._runOutsizeZone(() => { @@ -691,8 +704,8 @@ export class MatSlider extends _MatSliderMixinBase * as they're swiping across the screen. */ private _bindGlobalEvents(triggerEvent: TouchEvent | MouseEvent) { - if (typeof document !== 'undefined' && document) { - const body = document.body; + if (typeof this._document !== 'undefined' && this._document) { + const body = this._document.body; const isTouch = isTouchEvent(triggerEvent); const moveEventName = isTouch ? 'touchmove' : 'mousemove'; const endEventName = isTouch ? 'touchend' : 'mouseup'; @@ -710,8 +723,8 @@ export class MatSlider extends _MatSliderMixinBase /** Removes any global event listeners that we may have added. */ private _removeGlobalEvents() { - if (typeof document !== 'undefined' && document) { - const body = document.body; + if (typeof this._document !== 'undefined' && this._document) { + const body = this._document.body; body.removeEventListener('mousemove', this._pointerMove, activeEventOptions); body.removeEventListener('mouseup', this._pointerUp, activeEventOptions); body.removeEventListener('touchmove', this._pointerMove, activeEventOptions); diff --git a/src/material/snack-bar/snack-bar-container.scss b/src/material/snack-bar/snack-bar-container.scss index ead2b88b81c6..6e3380b46bae 100644 --- a/src/material/snack-bar/snack-bar-container.scss +++ b/src/material/snack-bar/snack-bar-container.scss @@ -19,7 +19,7 @@ $mat-snack-bar-spacing-margin-handset: 8px !default; min-height: $mat-snack-bar-min-height; transform-origin: center; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { border: solid 1px; } } diff --git a/src/material/sort/sort-header.scss b/src/material/sort/sort-header.scss index 70ad3f7a36ee..c1928991a26b 100644 --- a/src/material/sort/sort-header.scss +++ b/src/material/sort/sort-header.scss @@ -77,7 +77,7 @@ $mat-sort-header-arrow-hint-opacity: 0.38; display: flex; align-items: center; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { width: 0; border-left: solid $mat-sort-header-arrow-thickness; } @@ -100,7 +100,7 @@ $mat-sort-header-arrow-hint-opacity: 0.38; background: currentColor; transform: rotate(45deg); - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { width: 0; height: 0; border-top: solid $mat-sort-header-arrow-thickness; @@ -116,7 +116,7 @@ $mat-sort-header-arrow-hint-opacity: 0.38; position: absolute; top: 0; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { width: 0; height: 0; border-left: solid $mat-sort-header-arrow-pointer-length; diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 747879b632bb..d80636f8f3b2 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -7,7 +7,7 @@ */ import {Directionality} from '@angular/cdk/bidi'; -import {BooleanInput, NumberInput} from '@angular/cdk/coercion'; +import {BooleanInput} from '@angular/cdk/coercion'; import { CdkStep, CdkStepper, @@ -81,11 +81,6 @@ export class MatStep extends CdkStep implements ErrorStateMatcher { return originalErrorState || customErrorState; } - - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_completed: BooleanInput; } @@ -137,8 +132,6 @@ export class MatStepper extends CdkStepper implements AfterContentInit { static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } @Component({ @@ -171,8 +164,6 @@ export class MatHorizontalStepper extends MatStepper { static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } @Component({ @@ -209,6 +200,4 @@ export class MatVerticalStepper extends MatStepper { static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } diff --git a/src/material/table/BUILD.bazel b/src/material/table/BUILD.bazel index bcb06ba0db70..e7efd7f25498 100644 --- a/src/material/table/BUILD.bazel +++ b/src/material/table/BUILD.bazel @@ -19,7 +19,6 @@ ng_module( assets = [":table.css"], module_name = "@angular/material/table", deps = [ - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", "//src/cdk/coercion", diff --git a/src/material/table/cell.ts b/src/material/table/cell.ts index 627160eaf859..b915785339f4 100644 --- a/src/material/table/cell.ts +++ b/src/material/table/cell.ts @@ -63,7 +63,6 @@ export class MatColumnDef extends CdkColumnDef { @Input('matColumnDef') name: string; static ngAcceptInputType_sticky: BooleanInput; - static ngAcceptInputType_stickyEnd: BooleanInput; } /** Header cell template container that adds the right classes and role. */ diff --git a/src/material/table/table-module.ts b/src/material/table/table-module.ts index 19ecfd11d48f..4814b6255a4d 100644 --- a/src/material/table/table-module.ts +++ b/src/material/table/table-module.ts @@ -27,7 +27,6 @@ import { MatRowDef } from './row'; import {MatTextColumn} from './text-column'; -import {CommonModule} from '@angular/common'; import {MatCommonModule} from '@angular/material/core'; const EXPORTED_DECLARATIONS = [ @@ -59,7 +58,6 @@ const EXPORTED_DECLARATIONS = [ @NgModule({ imports: [ CdkTableModule, - CommonModule, MatCommonModule, ], exports: EXPORTED_DECLARATIONS, diff --git a/src/material/table/table.ts b/src/material/table/table.ts index 1febf4be832e..6ec188a94e0e 100644 --- a/src/material/table/table.ts +++ b/src/material/table/table.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table'; import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; @@ -30,6 +29,4 @@ import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/co export class MatTable extends CdkTable { /** Overrides the sticky CSS class set by the `CdkTable`. */ protected stickyCssClass = 'mat-table-sticky'; - - static ngAcceptInputType_multiTemplateDataRows: BooleanInput; } diff --git a/src/material/table/testing/row-harness.ts b/src/material/table/testing/row-harness.ts index 88c03f6d1f7f..c0b03d4b4472 100644 --- a/src/material/table/testing/row-harness.ts +++ b/src/material/table/testing/row-harness.ts @@ -10,6 +10,11 @@ import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; import {RowHarnessFilters, CellHarnessFilters} from './table-harness-filters'; import {MatCellHarness, MatHeaderCellHarness, MatFooterCellHarness} from './cell-harness'; +/** Text extracted from a table row organized by columns. */ +export interface MatRowHarnessColumnsText { + [columnName: string]: string; +} + /** Harness for interacting with a standard Angular Material table row. */ export class MatRowHarness extends ComponentHarness { /** The selector for the host element of a `MatRowHarness` instance. */ @@ -33,6 +38,11 @@ export class MatRowHarness extends ComponentHarness { async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { return getCellTextByIndex(this, filter); } + + /** Gets the text inside the row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } } /** Harness for interacting with a standard Angular Material table header row. */ @@ -59,6 +69,11 @@ export class MatHeaderRowHarness extends ComponentHarness { async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { return getCellTextByIndex(this, filter); } + + /** Gets the text inside the header row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } } @@ -86,11 +101,29 @@ export class MatFooterRowHarness extends ComponentHarness { async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { return getCellTextByIndex(this, filter); } + + /** Gets the text inside the footer row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } } + async function getCellTextByIndex(harness: { getCells: (filter?: CellHarnessFilters) => Promise }, filter: CellHarnessFilters): Promise { const cells = await harness.getCells(filter); return Promise.all(cells.map(cell => cell.getText())); } + +async function getCellTextByColumnName(harness: { + getCells: () => Promise +}): Promise { + const output: MatRowHarnessColumnsText = {}; + const cells = await harness.getCells(); + const cellsData = await Promise.all(cells.map(cell => { + return Promise.all([cell.getColumnName(), cell.getText()]); + })); + cellsData.forEach(([columnName, text]) => output[columnName] = text); + return output; +} diff --git a/src/material/table/testing/shared.spec.ts b/src/material/table/testing/shared.spec.ts index 8ac81fa76787..954d1e163127 100644 --- a/src/material/table/testing/shared.spec.ts +++ b/src/material/table/testing/shared.spec.ts @@ -138,6 +138,27 @@ export function runHarnessTests( ['10', 'Neon', '20.1797', 'Ne'] ]); }); + + it('should be able to get the cell text in a row organized by index', async () => { + const table = await loader.getHarness(tableHarness); + const rows = await table.getRows(); + + expect(rows.length).toBeGreaterThan(0); + expect(await rows[0].getCellTextByIndex()).toEqual(['1', 'Hydrogen', '1.0079', 'H']); + }); + + it('should be able to get the cell text in a row organized by columns', async () => { + const table = await loader.getHarness(tableHarness); + const rows = await table.getRows(); + + expect(rows.length).toBeGreaterThan(0); + expect(await rows[0].getCellTextByColumnName()).toEqual({ + position: '1', + name: 'Hydrogen', + weight: '1.0079', + symbol: 'H' + }); + }); } @Component({ diff --git a/src/material/table/testing/table-harness.ts b/src/material/table/testing/table-harness.ts index dcd5ff64f16c..6f9f8835b89d 100644 --- a/src/material/table/testing/table-harness.ts +++ b/src/material/table/testing/table-harness.ts @@ -8,7 +8,12 @@ import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; import {TableHarnessFilters, RowHarnessFilters} from './table-harness-filters'; -import {MatRowHarness, MatHeaderRowHarness, MatFooterRowHarness} from './row-harness'; +import { + MatRowHarness, + MatHeaderRowHarness, + MatFooterRowHarness, + MatRowHarnessColumnsText, +} from './row-harness'; /** Text extracted from a table organized by columns. */ export interface MatTableHarnessColumnsText { @@ -64,13 +69,15 @@ export class MatTableHarness extends ComponentHarness { const text: MatTableHarnessColumnsText = {}; const [headerData, footerData, rowsData] = await Promise.all([ - Promise.all(headerRows.map(row => getRowData(row))), - Promise.all(footerRows.map(row => getRowData(row))), - Promise.all(dataRows.map(row => getRowData(row))), + Promise.all(headerRows.map(row => row.getCellTextByColumnName())), + Promise.all(footerRows.map(row => row.getCellTextByColumnName())), + Promise.all(dataRows.map(row => row.getCellTextByColumnName())), ]); - rowsData.forEach(cells => { - cells.forEach(([columnName, cellText]) => { + rowsData.forEach(data => { + Object.keys(data).forEach(columnName => { + const cellText = data[columnName]; + if (!text[columnName]) { text[columnName] = { headerText: getCellTextsByColumn(headerData, columnName), @@ -87,21 +94,14 @@ export class MatTableHarness extends ComponentHarness { } } -/** Utility to extract the column names and text from all of the cells in a row. */ -async function getRowData(row: MatRowHarness | MatHeaderRowHarness | MatFooterRowHarness) { - const cells = await row.getCells(); - return Promise.all(cells.map(cell => Promise.all([cell.getColumnName(), cell.getText()]))); -} - - /** Extracts the text of cells only under a particular column. */ -function getCellTextsByColumn(rowsData: [string, string][][], column: string): string[] { +function getCellTextsByColumn(rowsData: MatRowHarnessColumnsText[], column: string): string[] { const columnTexts: string[] = []; - rowsData.forEach(cells => { - cells.forEach(([columnName, cellText]) => { + rowsData.forEach(data => { + Object.keys(data).forEach(columnName => { if (columnName === column) { - columnTexts.push(cellText); + columnTexts.push(data[columnName]); } }); }); diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index 8a5067d1adc5..b14b03fa64c2 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -27,15 +27,17 @@ $mat-tab-animation-duration: 500ms !default; opacity: 1; } - @include cdk-high-contrast { - outline: dotted 2px; + @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 { cursor: default; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 0.5; } } @@ -47,7 +49,7 @@ $mat-tab-animation-duration: 500ms !default; white-space: nowrap; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { opacity: 1; } } @@ -67,7 +69,7 @@ $mat-tab-animation-duration: 500ms !default; top: 0; } - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid $height; height: 0; } diff --git a/src/material/tabs/paginated-tab-header.ts b/src/material/tabs/paginated-tab-header.ts index 7da835e2df24..94f57d818186 100644 --- a/src/material/tabs/paginated-tab-header.ts +++ b/src/material/tabs/paginated-tab-header.ts @@ -22,7 +22,7 @@ import { Input, } from '@angular/core'; import {Direction, Directionality} from '@angular/cdk/bidi'; -import {coerceNumberProperty} from '@angular/cdk/coercion'; +import {coerceNumberProperty, NumberInput} from '@angular/cdk/coercion'; import {ViewportRuler} from '@angular/cdk/scrolling'; import {FocusKeyManager, FocusableOption} from '@angular/cdk/a11y'; import {END, ENTER, HOME, SPACE, hasModifierKey} from '@angular/cdk/keycodes'; @@ -549,7 +549,13 @@ export abstract class MatPaginatedTabHeader implements AfterContentChecked, Afte * Starts scrolling the header after a certain amount of time. * @param direction In which direction the paginator should be scrolled. */ - _handlePaginatorPress(direction: ScrollDirection) { + _handlePaginatorPress(direction: ScrollDirection, mouseEvent?: MouseEvent) { + // Don't start auto scrolling for right mouse button clicks. Note that we shouldn't have to + // null check the `button`, but we do it so we don't break tests that use fake events. + if (mouseEvent && mouseEvent.button != null && mouseEvent.button !== 0) { + return; + } + // Avoid overlapping timers. this._stopInterval(); @@ -587,4 +593,6 @@ export abstract class MatPaginatedTabHeader implements AfterContentChecked, Afte return {maxScrollDistance, distance: this._scrollDistance}; } + + static ngAcceptInputType_selectedIndex: NumberInput; } diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts index 0e929e3c9e67..1236829e8d64 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -375,6 +375,11 @@ export abstract class _MatTabGroupBase extends _MatTabGroupMixinBase implements } return this.selectedIndex === idx ? 0 : -1; } + + static ngAcceptInputType_dynamicHeight: BooleanInput; + static ngAcceptInputType_animationDuration: NumberInput; + static ngAcceptInputType_selectedIndex: NumberInput; + static ngAcceptInputType_disableRipple: BooleanInput; } /** @@ -412,9 +417,4 @@ export class MatTabGroup extends _MatTabGroupBase { @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { super(elementRef, changeDetectorRef, defaultConfig, animationMode); } - - static ngAcceptInputType_dynamicHeight: BooleanInput; - static ngAcceptInputType_animationDuration: NumberInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material/tabs/tab-header.html b/src/material/tabs/tab-header.html index 39abcf94cd2c..e3febcb6f045 100644 --- a/src/material/tabs/tab-header.html +++ b/src/material/tabs/tab-header.html @@ -4,7 +4,7 @@ mat-ripple [matRippleDisabled]="_disableScrollBefore || disableRipple" [class.mat-tab-header-pagination-disabled]="_disableScrollBefore" (click)="_handlePaginatorClick('before')" - (mousedown)="_handlePaginatorPress('before')" + (mousedown)="_handlePaginatorPress('before', $event)" (touchend)="_stopInterval()">
    @@ -28,7 +28,7 @@ aria-hidden="true" mat-ripple [matRippleDisabled]="_disableScrollAfter || disableRipple" [class.mat-tab-header-pagination-disabled]="_disableScrollAfter" - (mousedown)="_handlePaginatorPress('after')" + (mousedown)="_handlePaginatorPress('after', $event)" (click)="_handlePaginatorClick('after')" (touchend)="_stopInterval()">
    diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index 6687231904e0..cf47ce07fd26 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -7,6 +7,7 @@ import { dispatchKeyboardEvent, createKeyboardEvent, dispatchEvent, + createMouseEvent, } from '@angular/cdk/testing/private'; import {CommonModule} from '@angular/common'; import {Component, ViewChild} from '@angular/core'; @@ -458,6 +459,16 @@ describe('MatTabHeader', () => { expect(header.scrollDistance).toBe(previousDistance); })); + it('should not scroll when pressing the right mouse button', fakeAsync(() => { + expect(header.scrollDistance).toBe(0, 'Expected to start off not scrolled.'); + + dispatchEvent(nextButton, createMouseEvent('mousedown', undefined, undefined, 2)); + fixture.detectChanges(); + tick(3000); + + expect(header.scrollDistance).toBe(0, 'Expected not to have scrolled after a while.'); + })); + /** * Asserts that auto scrolling using the next button works. * @param startEventName Name of the event that is supposed to start the scrolling. diff --git a/src/material/tabs/tab-header.ts b/src/material/tabs/tab-header.ts index 883c31da84c7..160f65538038 100644 --- a/src/material/tabs/tab-header.ts +++ b/src/material/tabs/tab-header.ts @@ -28,7 +28,7 @@ import { Directive, } from '@angular/core'; import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; -import {BooleanInput, coerceBooleanProperty, NumberInput} from '@angular/cdk/coercion'; +import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {MatInkBar} from './ink-bar'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {Platform} from '@angular/cdk/platform'; @@ -107,5 +107,4 @@ export class MatTabHeader extends _MatTabHeaderBase { } static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.html b/src/material/tabs/tab-nav-bar/tab-nav-bar.html index 8d65b9139400..e905dce2d78f 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.html +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.html @@ -4,7 +4,7 @@ mat-ripple [matRippleDisabled]="_disableScrollBefore || disableRipple" [class.mat-tab-header-pagination-disabled]="_disableScrollBefore" (click)="_handlePaginatorClick('before')" - (mousedown)="_handlePaginatorPress('before')" + (mousedown)="_handlePaginatorPress('before', $event)" (touchend)="_stopInterval()">
    @@ -23,7 +23,7 @@ aria-hidden="true" mat-ripple [matRippleDisabled]="_disableScrollAfter || disableRipple" [class.mat-tab-header-pagination-disabled]="_disableScrollAfter" - (mousedown)="_handlePaginatorPress('after')" + (mousedown)="_handlePaginatorPress('after', $event)" (click)="_handlePaginatorClick('after')" (touchend)="_stopInterval()">
    diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts index 16db69df9834..5e5c567ddf07 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts @@ -41,7 +41,7 @@ import { RippleTarget, ThemePalette, } from '@angular/material/core'; -import {BooleanInput, coerceBooleanProperty, NumberInput} from '@angular/cdk/coercion'; +import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {FocusMonitor, FocusableOption} from '@angular/cdk/a11y'; import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; import {MatInkBar} from '../ink-bar'; @@ -181,7 +181,6 @@ export class MatTabNav extends _MatTabNavBase { } static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; } // Boilerplate for applying mixins to MatTabLink. @@ -250,6 +249,9 @@ export class _MatTabLinkBase extends _MatTabLinkMixinBase implements OnDestroy, ngOnDestroy() { this._focusMonitor.stopMonitoring(this.elementRef); } + + static ngAcceptInputType_disabled: BooleanInput; + static ngAcceptInputType_disableRipple: BooleanInput; } @@ -288,7 +290,4 @@ export class MatTabLink extends _MatTabLinkBase implements OnDestroy { super.ngOnDestroy(); this._tabLinkRipple._removeTriggerEvents(); } - - static ngAcceptInputType_disabled: BooleanInput; - static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material/toolbar/toolbar.scss b/src/material/toolbar/toolbar.scss index aa2e9211569f..7e51384cdad1 100644 --- a/src/material/toolbar/toolbar.scss +++ b/src/material/toolbar/toolbar.scss @@ -22,7 +22,7 @@ $mat-toolbar-height-mobile-landscape: 48px !default; } .mat-toolbar { - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/tooltip/tooltip.scss b/src/material/tooltip/tooltip.scss index 1057db33775d..2779d0c3921a 100644 --- a/src/material/tooltip/tooltip.scss +++ b/src/material/tooltip/tooltip.scss @@ -25,7 +25,7 @@ $mat-tooltip-handset-margin: 24px; overflow: hidden; text-overflow: ellipsis; - @include cdk-high-contrast { + @include cdk-high-contrast(active, off) { outline: solid 1px; } } diff --git a/src/material/tree/BUILD.bazel b/src/material/tree/BUILD.bazel index e0f4249363ec..af408f483cee 100644 --- a/src/material/tree/BUILD.bazel +++ b/src/material/tree/BUILD.bazel @@ -22,7 +22,6 @@ ng_module( "//src/cdk/collections", "//src/cdk/tree", "//src/material/core", - "@npm//@angular/common", "@npm//@angular/core", "@npm//rxjs", ], diff --git a/src/material/tree/padding.ts b/src/material/tree/padding.ts index 7a96c68b0d66..9284b2b76a99 100644 --- a/src/material/tree/padding.ts +++ b/src/material/tree/padding.ts @@ -5,7 +5,6 @@ * 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 {NumberInput} from '@angular/cdk/coercion'; import {CdkTreeNodePadding} from '@angular/cdk/tree'; import {Directive, Input} from '@angular/core'; @@ -23,6 +22,4 @@ export class MatTreeNodePadding extends CdkTreeNodePadding { /** The indent for each level. Default number 40px from material design menu sub-menu spec. */ @Input('matTreeNodePaddingIndent') indent: number; - - static ngAcceptInputType_level: NumberInput; } diff --git a/src/material/tree/toggle.ts b/src/material/tree/toggle.ts index 2da5f3509685..5c7b9f9a2479 100644 --- a/src/material/tree/toggle.ts +++ b/src/material/tree/toggle.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {BooleanInput} from '@angular/cdk/coercion'; import {CdkTreeNodeToggle} from '@angular/cdk/tree'; import {Directive, Input} from '@angular/core'; @@ -19,6 +18,4 @@ import {Directive, Input} from '@angular/core'; }) export class MatTreeNodeToggle extends CdkTreeNodeToggle { @Input('matTreeNodeToggleRecursive') recursive: boolean = false; - - static ngAcceptInputType_recursive: BooleanInput; } diff --git a/src/material/tree/tree-module.ts b/src/material/tree/tree-module.ts index 53d23c0fa384..63eea3458d9e 100644 --- a/src/material/tree/tree-module.ts +++ b/src/material/tree/tree-module.ts @@ -9,7 +9,6 @@ import {NgModule} from '@angular/core'; import {CdkTreeModule} from '@angular/cdk/tree'; -import {CommonModule} from '@angular/common'; import {MatCommonModule} from '@angular/material/core'; import {MatNestedTreeNode, MatTreeNodeDef, MatTreeNode} from './node'; import {MatTree} from './tree'; @@ -28,7 +27,7 @@ const MAT_TREE_DIRECTIVES = [ ]; @NgModule({ - imports: [CdkTreeModule, CommonModule, MatCommonModule], + imports: [CdkTreeModule, MatCommonModule], exports: MAT_TREE_DIRECTIVES, declarations: MAT_TREE_DIRECTIVES, }) diff --git a/src/material/tree/tree.scss b/src/material/tree/tree.scss index 64a229f35448..80822f8664be 100644 --- a/src/material/tree/tree.scss +++ b/src/material/tree/tree.scss @@ -9,7 +9,6 @@ $mat-node-height: 48px; align-items: center; min-height: $mat-node-height; flex: 1; - overflow: hidden; word-wrap: break-word; } diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index de757a80014b..c9b3ad2d9e36 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -685,7 +685,7 @@ const TREE_DATA: FoodNode[] = [ name: 'Green', children: [ {name: 'Broccoli'}, - {name: 'Brussel sprouts'}, + {name: 'Brussels sprouts'}, ] }, { name: 'Orange', diff --git a/src/universal-app/BUILD.bazel b/src/universal-app/BUILD.bazel index a9c221a35cb9..db790eaf9c4b 100644 --- a/src/universal-app/BUILD.bazel +++ b/src/universal-app/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_test") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") load("//src/cdk:config.bzl", "CDK_TARGETS") load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_TARGETS") load("//src/material:config.bzl", "MATERIAL_TARGETS") 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 52cdc40c4d57..015d8e742849 100644 --- a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html +++ b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html @@ -27,8 +27,18 @@

    MDC checkbox

    MDC chips

    - -Not yet implemented. + + Basic Chip 1 + Basic Chip 2 + Basic Chip 3 + + + + Extra Small + Small + Medium + Large +

    MDC menu

    @@ -76,11 +86,36 @@

    Tabs

    Progress bar

    - - + + + +

    Form field

    + + Label + + + + + + + Always floating + + + + Label + + + + Label + + + + Label + + 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 7da0f993149c..18950e541b62 100644 --- a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts +++ b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts @@ -2,6 +2,8 @@ import {Component, NgModule, ErrorHandler} from '@angular/core'; import {MatButtonModule} from '@angular/material-experimental/mdc-button'; import {MatCardModule} from '@angular/material-experimental/mdc-card'; import {MatCheckboxModule} from '@angular/material-experimental/mdc-checkbox'; +import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatInputModule} from '@angular/material-experimental/mdc-input'; import {MatProgressBarModule} from '@angular/material-experimental/mdc-progress-bar'; import {MatChipsModule} from '@angular/material-experimental/mdc-chips'; import {MatMenuModule} from '@angular/material-experimental/mdc-menu'; @@ -24,7 +26,9 @@ export class KitchenSinkMdc { MatCardModule, MatCheckboxModule, MatChipsModule, + MatFormFieldModule, MatIconModule, + MatInputModule, MatMenuModule, MatRadioModule, MatSlideToggleModule, diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index df4600ce97e0..961c26cea574 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -157,7 +157,7 @@

    Menu

    Progress bar

    - + @@ -225,7 +225,7 @@

    Tabs

    Paginator

    @@ -384,4 +384,12 @@

    YouTube player

    Google Map

    - + + + Hello + + diff --git a/src/universal-app/prerender.ts b/src/universal-app/prerender.ts index 62c4cbc26917..70b3dc03210b 100644 --- a/src/universal-app/prerender.ts +++ b/src/universal-app/prerender.ts @@ -26,7 +26,8 @@ result writeFileSync(filename, content, 'utf-8'); console.log('Prerender done.'); }) - // If rendering the module factory fails, exit the process with an error code because otherwise - // the CI task will not recognize the failure and will show as "success". The error message - // will be printed automatically by the `renderModuleFactory` method. - .catch(() => process.exit(1)); + // If rendering the module factory fails, re-throw the error in order to print the + // failure to the console, and to exit the process with a non-zero exit code. + .catch(error => { + throw error; + }); diff --git a/src/youtube-player/youtube-player.spec.ts b/src/youtube-player/youtube-player.spec.ts index 02bed6153cb2..3a11964e20ef 100644 --- a/src/youtube-player/youtube-player.spec.ts +++ b/src/youtube-player/youtube-player.spec.ts @@ -22,7 +22,7 @@ describe('YoutubePlayer', () => { TestBed.configureTestingModule({ imports: [YouTubePlayerModule], - declarations: [TestApp], + declarations: [TestApp, StaticStartEndSecondsApp], }); TestBed.compileComponents(); @@ -326,6 +326,17 @@ describe('YoutubePlayer', () => { }); }); + it('should pick up static startSeconds and endSeconds values', () => { + const staticSecondsApp = TestBed.createComponent(StaticStartEndSecondsApp); + staticSecondsApp.detectChanges(); + + playerSpy.getPlayerState.and.returnValue(window.YT!.PlayerState.CUED); + events.onReady({target: playerSpy}); + + expect(playerSpy.cueVideoById).toHaveBeenCalledWith( + jasmine.objectContaining({startSeconds: 42, endSeconds: 1337})); + }); + }); /** Test component that contains a YouTubePlayer. */ @@ -359,3 +370,13 @@ class TestApp { onApiChange = jasmine.createSpy('onApiChange'); @ViewChild('player') youtubePlayer: YouTubePlayer; } + + +@Component({ + template: ` + + ` +}) +class StaticStartEndSecondsApp { + videoId = VIDEO_ID; +} diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index 36c665750705..071a372e4492 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -39,6 +39,7 @@ import { pipe, Subject, of, + BehaviorSubject, } from 'rxjs'; import { @@ -91,51 +92,48 @@ type UninitializedPlayer = Pick(); + private _videoId = new BehaviorSubject(undefined); /** Height of video player */ @Input() - get height(): number | undefined { return this._height; } + get height(): number | undefined { return this._height.value; } set height(height: number | undefined) { - this._height = height || DEFAULT_PLAYER_HEIGHT; - this._heightObs.next(this._height); + this._height.next(height || DEFAULT_PLAYER_HEIGHT); } - private _height = DEFAULT_PLAYER_HEIGHT; - private _heightObs = new Subject(); + private _height = new BehaviorSubject(DEFAULT_PLAYER_HEIGHT); /** Width of video player */ @Input() - get width(): number | undefined { return this._width; } + get width(): number | undefined { return this._width.value; } set width(width: number | undefined) { - this._width = width || DEFAULT_PLAYER_WIDTH; - this._widthObs.next(this._width); + this._width.next(width || DEFAULT_PLAYER_WIDTH); } - private _width = DEFAULT_PLAYER_WIDTH; - private _widthObs = new Subject(); + private _width = new BehaviorSubject(DEFAULT_PLAYER_WIDTH); /** The moment when the player is supposed to start playing */ - @Input() set startSeconds(startSeconds: number | undefined) { + @Input() + set startSeconds(startSeconds: number | undefined) { this._startSeconds.next(startSeconds); } - private _startSeconds = new Subject(); + private _startSeconds = new BehaviorSubject(undefined); /** The moment when the player is supposed to stop playing */ - @Input() set endSeconds(endSeconds: number | undefined) { + @Input() + set endSeconds(endSeconds: number | undefined) { this._endSeconds.next(endSeconds); } - private _endSeconds = new Subject(); + private _endSeconds = new BehaviorSubject(undefined); /** The suggested quality of the player */ - @Input() set suggestedQuality(suggestedQuality: YT.SuggestedVideoQuality | undefined) { + @Input() + set suggestedQuality(suggestedQuality: YT.SuggestedVideoQuality | undefined) { this._suggestedQuality.next(suggestedQuality); } - private _suggestedQuality = new Subject(); + private _suggestedQuality = new BehaviorSubject(undefined); /** * Whether the iframe will attempt to load regardless of the status of the api on the @@ -202,40 +200,36 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { iframeApiAvailableObs = iframeApiAvailableSubject.pipe(take(1), startWith(false)); } - // Add initial values to all of the inputs. - const videoIdObs = this._videoIdObs.pipe(startWith(this._videoId)); - const widthObs = this._widthObs.pipe(startWith(this._width)); - const heightObs = this._heightObs.pipe(startWith(this._height)); - - const startSecondsObs = this._startSeconds.pipe(startWith(undefined)); - const endSecondsObs = this._endSeconds.pipe(startWith(undefined)); - const suggestedQualityObs = this._suggestedQuality.pipe(startWith(undefined)); - // An observable of the currently loaded player. const playerObs = createPlayerObservable( this._youtubeContainer, - videoIdObs, + this._videoId, iframeApiAvailableObs, - widthObs, - heightObs, + this._width, + this._height, this.createEventsBoundInZone(), this._ngZone - ).pipe(waitUntilReady(), takeUntil(this._destroyed), publish()); + ).pipe(waitUntilReady(player => { + // Destroy the player if loading was aborted so that we don't end up leaking memory. + if (!playerIsReady(player)) { + player.destroy(); + } + }), takeUntil(this._destroyed), publish()); // Set up side effects to bind inputs to the player. playerObs.subscribe(player => this._player = player); - bindSizeToPlayer(playerObs, widthObs, heightObs); + bindSizeToPlayer(playerObs, this._width, this._height); - bindSuggestedQualityToPlayer(playerObs, suggestedQualityObs); + bindSuggestedQualityToPlayer(playerObs, this._suggestedQuality); bindCueVideoCall( playerObs, - videoIdObs, - startSecondsObs, - endSecondsObs, - suggestedQualityObs, + this._videoId, + this._startSeconds, + this._endSeconds, + this._suggestedQuality, this._destroyed); // After all of the subscriptions are set up, connect the observable. @@ -273,9 +267,9 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { window.onYouTubeIframeAPIReady = this._existingApiReadyCallback; } - this._videoIdObs.complete(); - this._heightObs.complete(); - this._widthObs.complete(); + this._videoId.complete(); + this._height.complete(); + this._width.complete(); this._startSeconds.complete(); this._endSeconds.complete(); this._suggestedQuality.complete(); @@ -438,39 +432,43 @@ function bindSuggestedQualityToPlayer( /** * Returns an observable that emits the loaded player once it's ready. Certain properties/methods * won't be available until the iframe finishes loading. + * @param onAbort Callback function that will be invoked if the player loading was aborted before + * it was able to complete. Can be used to clean up any loose references. */ -function waitUntilReady(): OperatorFunction { +function waitUntilReady(onAbort: (player: UninitializedPlayer) => void): + OperatorFunction { return flatMap(player => { if (!player) { return observableOf(undefined); } - if ('getPlayerStatus' in player) { + if (playerIsReady(player)) { return observableOf(player as Player); } - // The player is not initialized fully until the ready is called. - return fromPlayerOnReady(player) - .pipe(take(1), startWith(undefined)); - }); -} -/** Since removeEventListener is not on Player when it's initialized, we can't use fromEvent. */ -function fromPlayerOnReady(player: UninitializedPlayer): Observable { - return new Observable(emitter => { - let aborted = false; + // Since removeEventListener is not on Player when it's initialized, we can't use fromEvent. + // The player is not initialized fully until the ready is called. + return new Observable(emitter => { + let aborted = false; + let resolved = false; + const onReady = (event: YT.PlayerEvent) => { + resolved = true; + + if (!aborted) { + event.target.removeEventListener('onReady', onReady); + emitter.next(event.target); + } + }; - const onReady = (event: YT.PlayerEvent) => { - if (aborted) { - return; - } - event.target.removeEventListener('onReady', onReady); - emitter.next(event.target); - }; + player.addEventListener('onReady', onReady); - player.addEventListener('onReady', onReady); + return () => { + aborted = true; - return () => { - aborted = true; - }; + if (!resolved) { + onAbort(player); + } + }; + }).pipe(take(1), startWith(undefined)); }); } @@ -584,7 +582,12 @@ function bindCueVideoCall( } function hasPlayerStarted(player: YT.Player): boolean { - return [YT.PlayerState.UNSTARTED, YT.PlayerState.CUED].indexOf(player.getPlayerState()) === -1; + const state = player.getPlayerState(); + return state !== YT.PlayerState.UNSTARTED && state !== YT.PlayerState.CUED; +} + +function playerIsReady(player: UninitializedPlayer): player is Player { + return 'getPlayerStatus' in player; } /** Combines the two observables temporarily for the filter function. */ diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 33672b729411..7bfa806ac2ba 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -1,5 +1,6 @@ package(default_visibility = ["//visibility:public"]) +load("//tools:create-system-config.bzl", "create_system_config") load("//tools:defaults.bzl", "ts_library") exports_files(["bazel-karma-local-config.js"]) @@ -19,3 +20,14 @@ ts_library( "@npm//@types/jasmine", ], ) + +# Creates a SystemJS configuration file that can be used for the Karma legacy +# unit tests. This allows us to only have one canonical SystemJS configuration +# file, and we don't need to repeat package/entry-point configurations. +create_system_config( + name = "system-config", + base_url = "base/", + node_modules_base_path = "node_modules/", + output_name = "system-config.js", + packages_dir = "dist/packages", +) diff --git a/test/bazel-karma-local-config.js b/test/bazel-karma-local-config.js index 413da59683ae..2f434de70ec6 100644 --- a/test/bazel-karma-local-config.js +++ b/test/bazel-karma-local-config.js @@ -3,6 +3,8 @@ * want to launch any browser and just enable manual browser debugging. */ +const bazelKarma = require('@bazel/karma'); + module.exports = config => { const overwrites = {}; @@ -19,9 +21,17 @@ module.exports = config => { // Ensures that tests start executing once browsers have been manually connected. We need // to use "defineProperty" because the default "@bazel/karma" config overwrites the option. Object.defineProperty(overwrites, 'autoWatch', { - value: true, - writable: false, + get: () => true, + set: () => {}, + enumerable: true, }); + // When not running with ibazel, do not set up the `@bazel/karma` watcher. This one + // relies on ibazel to write to the `stdin` interface. When running without ibazel, the + // watcher will kill Karma since there is no data written to the `stdin` interface. + if (process.env['IBAZEL_NOTIFY_CHANGES'] !== 'y') { + delete bazelKarma['watcher']; + } + config.set(overwrites); }; diff --git a/test/karma-system-config.js b/test/karma-system-config.js deleted file mode 100644 index a4a0539a7cdc..000000000000 --- a/test/karma-system-config.js +++ /dev/null @@ -1,240 +0,0 @@ -// Configure the base path and map the different node packages. -System.config({ - baseURL: '/base', - paths: {'node:*': 'node_modules/*'}, - map: { - 'rxjs': 'node:rxjs', - 'tslib': 'node:tslib/tslib.js', - 'moment': 'node:moment/min/moment-with-locales.min.js', - - // MDC Web - '@material/animation': 'node:@material/animation/dist/mdc.animation.js', - '@material/auto-init': 'node:@material/auto-init/dist/mdc.autoInit.js', - '@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/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', - '@material/floating-label': 'node:@material/floating-label/dist/mdc.floatingLabel.js', - '@material/form-field': 'node:@material/form-field/dist/mdc.formField.js', - '@material/grid-list': 'node:@material/grid-list/dist/mdc.gridList.js', - '@material/icon-button': 'node:@material/icon-button/dist/mdc.iconButton.js', - '@material/line-ripple': 'node:@material/line-ripple/dist/mdc.lineRipple.js', - '@material/linear-progress': 'node:@material/linear-progress/dist/mdc.linearProgress.js', - '@material/list': 'node:@material/list/dist/mdc.list.js', - '@material/menu': 'node:@material/menu/dist/mdc.menu.js', - '@material/menu-surface': 'node:@material/menu-surface/dist/mdc.menuSurface.js', - '@material/notched-outline': 'node:@material/notched-outline/dist/mdc.notchedOutline.js', - '@material/radio': 'node:@material/radio/dist/mdc.radio.js', - '@material/ripple': 'node:@material/ripple/dist/mdc.ripple.js', - '@material/select': 'node:@material/select/dist/mdc.select.js', - '@material/slider': 'node:@material/slider/dist/mdc.slider.js', - '@material/snackbar': 'node:@material/snackbar/dist/mdc.snackbar.js', - '@material/switch': 'node:@material/switch/dist/mdc.switch.js', - '@material/tab': 'node:@material/tab/dist/mdc.tab.js', - '@material/tab-bar': 'node:@material/tab-bar/dist/mdc.tabBar.js', - '@material/tab-indicator': 'node:@material/tab-indicator/dist/mdc.tabIndicator.js', - '@material/tab-scroller': 'node:@material/tab-scroller/dist/mdc.tabScroller.js', - '@material/text-field': 'node:@material/textfield/dist/mdc.textField.js', - '@material/top-app-bar': 'node:@material/top-app-bar/dist/mdc.topAppBar.js', - - // Angular specific mappings. - '@angular/core': 'node:@angular/core/__ivy_ngcc__/bundles/core.umd.js', - '@angular/core/testing': 'node:@angular/core/__ivy_ngcc__/bundles/core-testing.umd.js', - '@angular/common': 'node:@angular/common/__ivy_ngcc__/bundles/common.umd.js', - '@angular/common/testing': 'node:@angular/common/__ivy_ngcc__/bundles/common-testing.umd.js', - '@angular/common/http': 'node:@angular/common/__ivy_ngcc__/bundles/common-http.umd.js', - '@angular/common/http/testing': 'node:@angular/common/__ivy_ngcc__/bundles/common-http-testing.umd.js', - '@angular/compiler': 'node:@angular/compiler/bundles/compiler.umd.js', - '@angular/compiler/testing': 'node:@angular/compiler/__ivy_ngcc__/bundles/compiler-testing.umd.js', - '@angular/forms': 'node:@angular/forms/__ivy_ngcc__/bundles/forms.umd.js', - '@angular/forms/testing': 'node:@angular/forms/__ivy_ngcc__/bundles/forms-testing.umd.js', - '@angular/animations': 'node:@angular/animations/__ivy_ngcc__/bundles/animations.umd.js', - '@angular/animations/browser': - 'node:@angular/animations/__ivy_ngcc__/bundles/animations-browser.umd.js', - '@angular/platform-browser/animations': - 'node:@angular/platform-browser/__ivy_ngcc__/bundles/platform-browser-animations.umd.js', - '@angular/platform-browser': - 'node:@angular/platform-browser/__ivy_ngcc__/bundles/platform-browser.umd.js', - '@angular/platform-browser/testing': - 'node:@angular/platform-browser/__ivy_ngcc__/bundles/platform-browser-testing.umd.js', - '@angular/platform-browser-dynamic': - 'node:@angular/platform-browser-dynamic/__ivy_ngcc__/bundles/platform-browser-dynamic.umd.js', - '@angular/platform-browser-dynamic/testing': - 'node:@angular/platform-browser-dynamic/__ivy_ngcc__/bundles/platform-browser-dynamic-testing.umd.js', - - // Path mappings for local packages that can be imported inside of tests. - '@angular/material': 'dist/packages/material/index.js', - '@angular/material-experimental': 'dist/packages/material-experimental/index.js', - '@angular/cdk-experimental': 'dist/packages/cdk-experimental/index.js', - - '@angular/cdk': 'dist/packages/cdk/index.js', - '@angular/cdk/a11y': 'dist/packages/cdk/a11y/index.js', - '@angular/cdk/accordion': 'dist/packages/cdk/accordion/index.js', - '@angular/cdk/bidi': 'dist/packages/cdk/bidi/index.js', - '@angular/cdk/cliboard': 'dist/packages/cdk/cliboard/index.js', - '@angular/cdk/coercion': 'dist/packages/cdk/coercion/index.js', - '@angular/cdk/collections': 'dist/packages/cdk/collections/index.js', - '@angular/cdk/drag-drop': 'dist/packages/cdk/drag-drop/index.js', - '@angular/cdk/testing/private': 'dist/packages/cdk/testing/private/index.js', - '@angular/cdk/keycodes': 'dist/packages/cdk/keycodes/index.js', - '@angular/cdk/layout': 'dist/packages/cdk/layout/index.js', - '@angular/cdk/observers': 'dist/packages/cdk/observers/index.js', - '@angular/cdk/overlay': 'dist/packages/cdk/overlay/index.js', - '@angular/cdk/platform': 'dist/packages/cdk/platform/index.js', - '@angular/cdk/portal': 'dist/packages/cdk/portal/index.js', - '@angular/cdk/scrolling': 'dist/packages/cdk/scrolling/index.js', - '@angular/cdk/stepper': 'dist/packages/cdk/stepper/index.js', - '@angular/cdk/table': 'dist/packages/cdk/table/index.js', - '@angular/cdk/testing': 'dist/packages/cdk/testing/index.js', - '@angular/cdk/testing/testbed': 'dist/packages/cdk/testing/testbed/index.js', - '@angular/cdk/testing/protractor': 'dist/packages/cdk/testing/protractor/index.js', - '@angular/cdk/text-field': 'dist/packages/cdk/text-field/index.js', - '@angular/cdk/tree': 'dist/packages/cdk/tree/index.js', - - '@angular/cdk-experimental/dialog': 'dist/packages/cdk-experimental/dialog/index.js', - '@angular/cdk-experimental/popover-edit': 'dist/packages/cdk-experimental/popover-edit/index.js', - '@angular/cdk-experimental/scrolling': 'dist/packages/cdk-experimental/scrolling/index.js', - - '@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js', - '@angular/material/autocomplete/testing': 'dist/packages/material/autocomplete/testing/index.js', - '@angular/material/autocomplete/testing/shared.spec': 'dist/packages/material/autocomplete/testing/shared.spec.js', - '@angular/material/badge': 'dist/packages/material/badge/index.js', - '@angular/material/badge/testing': 'dist/packages/material/badge/testing/index.js', - '@angular/material/badge/testing/shared.spec': 'dist/packages/material/badge/testing/shared.spec.js', - '@angular/material/bottom-sheet': 'dist/packages/material/bottom-sheet/index.js', - '@angular/material/bottom-sheet/testing': 'dist/packages/material/bottom-sheet/testing/index.js', - '@angular/material/bottom-sheet/testing/shared.spec': 'dist/packages/material/bottom-sheet/testing/shared.spec.js', - '@angular/material/button': 'dist/packages/material/button/index.js', - '@angular/material/button/testing': 'dist/packages/material/button/testing/index.js', - '@angular/material/button/testing/shared.spec': 'dist/packages/material/button/testing/shared.spec.js', - '@angular/material/button-toggle': 'dist/packages/material/button-toggle/index.js', - '@angular/material/button-toggle/testing': 'dist/packages/material/button-toggle/testing/index.js', - '@angular/material/button-toggle/testing/button-toggle-shared.spec': - 'dist/packages/material/button-toggle/testing/button-toggle-shared.spec.js', - '@angular/material/button-toggle/testing/button-toggle-group.spec': - 'dist/packages/material/button-toggle/testing/button-toggle-group.spec.js', - '@angular/material/card': 'dist/packages/material/card/index.js', - '@angular/material/checkbox': 'dist/packages/material/checkbox/index.js', - '@angular/material/checkbox/testing': 'dist/packages/material/checkbox/testing/index.js', - '@angular/material/checkbox/testing/shared.spec': 'dist/packages/material/checkbox/testing/shared.spec.js', - '@angular/material/chips': 'dist/packages/material/chips/index.js', - '@angular/material/core': 'dist/packages/material/core/index.js', - '@angular/material/core/testing': 'dist/packages/material/core/testing/index.js', - '@angular/material/core/testing/option-shared.spec': 'dist/packages/material/core/testing/option-shared.spec.js', - '@angular/material/core/testing/optgroup-shared.spec': 'dist/packages/material/core/testing/optgroup-shared.spec.js', - '@angular/material/datepicker': 'dist/packages/material/datepicker/index.js', - '@angular/material/dialog': 'dist/packages/material/dialog/index.js', - '@angular/material/dialog/testing': 'dist/packages/material/dialog/testing/index.js', - '@angular/material/dialog/testing/shared.spec': 'dist/packages/material/dialog/testing/shared.spec.js', - '@angular/material/divider': 'dist/packages/material/divider/index.js', - '@angular/material/divider/testing': 'dist/packages/material/divider/testing/index.js', - '@angular/material/expansion': 'dist/packages/material/expansion/index.js', - '@angular/material/form-field': 'dist/packages/material/form-field/index.js', - '@angular/material/grid-list': 'dist/packages/material/grid-list/index.js', - '@angular/material/icon': 'dist/packages/material/icon/index.js', - '@angular/material/input': 'dist/packages/material/input/index.js', - '@angular/material/list': 'dist/packages/material/list/index.js', - '@angular/material/list/testing': 'dist/packages/material/list/testing/index.js', - '@angular/material/menu': 'dist/packages/material/menu/index.js', - '@angular/material/menu/testing': 'dist/packages/material/menu/testing/index.js', - '@angular/material/menu/testing/shared.spec': 'dist/packages/material/menu/testing/shared.spec.js', - '@angular/material/paginator': 'dist/packages/material/paginator/index.js', - '@angular/material/paginator/testing': 'dist/packages/material/paginator/testing/index.js', - '@angular/material/paginator/testing/shared.spec': 'dist/packages/material/paginator/testing/shared.spec.js', - '@angular/material/progress-bar': 'dist/packages/material/progress-bar/index.js', - '@angular/material/progress-bar/testing': 'dist/packages/material/progress-bar/testing/index.js', - '@angular/material/progress-bar/testing/shared.spec': 'dist/packages/material/progress-bar/testing/shared.spec.js', - '@angular/material/progress-spinner': 'dist/packages/material/progress-spinner/index.js', - '@angular/material/progress-spinner/testing': 'dist/packages/material/progress-spinner/testing/index.js', - '@angular/material/progress-spinner/testing/shared.spec': 'dist/packages/material/progress-spinner/testing/shared.spec.js', - '@angular/material/radio': 'dist/packages/material/radio/index.js', - '@angular/material/radio/testing': 'dist/packages/material/radio/testing/index.js', - '@angular/material/radio/testing/shared.spec': 'dist/packages/material/radio/testing/shared.spec.js', - '@angular/material/select': 'dist/packages/material/select/index.js', - '@angular/material/sidenav': 'dist/packages/material/sidenav/index.js', - '@angular/material/sidenav/testing': 'dist/packages/material/sidenav/testing/index.js', - '@angular/material/sidenav/testing/shared.spec': 'dist/packages/material/sidenav/testing/shared.spec.js', - '@angular/material/slide-toggle': 'dist/packages/material/slide-toggle/index.js', - '@angular/material/slide-toggle/testing': 'dist/packages/material/slide-toggle/testing/index.js', - '@angular/material/slide-toggle/testing/shared.spec': 'dist/packages/material/slide-toggle/testing/shared.spec.js', - '@angular/material/slider': 'dist/packages/material/slider/index.js', - '@angular/material/slider/testing': 'dist/packages/material/slider/testing/index.js', - '@angular/material/slider/testing/shared.spec': 'dist/packages/material/slider/testing/shared.spec.js', - '@angular/material/snack-bar': 'dist/packages/material/snack-bar/index.js', - '@angular/material/snack-bar/testing': 'dist/packages/material/snack-bar/testing/index.js', - '@angular/material/snack-bar/testing/shared.spec': 'dist/packages/material/snack-bar/testing/shared.spec.js', - '@angular/material/sort': 'dist/packages/material/sort/index.js', - '@angular/material/sort/testing': 'dist/packages/material/sort/testing/index.js', - '@angular/material/sort/testing/shared.spec': 'dist/packages/material/sort/testing/shared.spec.js', - '@angular/material/stepper': 'dist/packages/material/stepper/index.js', - '@angular/material/table': 'dist/packages/material/table/index.js', - '@angular/material/table/testing': 'dist/packages/material/table/testing/index.js', - '@angular/material/table/testing/shared.spec': 'dist/packages/material/table/testing/shared.spec.js', - '@angular/material/tabs': 'dist/packages/material/tabs/index.js', - '@angular/material/tabs/testing': 'dist/packages/material/tabs/testing/index.js', - '@angular/material/tabs/testing/shared.spec': 'dist/packages/material/tabs/testing/shared.spec.js', - '@angular/material/testing': 'dist/packages/material/testing/index.js', - '@angular/material/toolbar': 'dist/packages/material/toolbar/index.js', - '@angular/material/tooltip': 'dist/packages/material/tooltip/index.js', - '@angular/material/tree': 'dist/packages/material/tree/index.js', - - '@angular/material/form-field/testing': - 'dist/packages/material/form-field/testing/index.js', - '@angular/material/form-field/testing/control': - 'dist/packages/material/form-field/testing/control/index.js', - '@angular/material/form-field/testing/shared.spec': - 'dist/packages/material/form-field/testing/shared.spec.js', - '@angular/material/input/testing': - 'dist/packages/material/input/testing/index.js', - '@angular/material-experimental/mdc-autocomplete': - 'dist/packages/material-experimental/mdc-autocomplete/index.js', - '@angular/material-experimental/mdc-button': - 'dist/packages/material-experimental/mdc-button/index.js', - '@angular/material-experimental/mdc-card': - 'dist/packages/material-experimental/mdc-card/index.js', - '@angular/material-experimental/mdc-checkbox': - 'dist/packages/material-experimental/mdc-checkbox/index.js', - '@angular/material-experimental/mdc-chips': - 'dist/packages/material-experimental/mdc-chips/index.js', - '@angular/material-experimental/mdc-helpers': - 'dist/packages/material-experimental/mdc-helpers/index.js', - '@angular/material-experimental/mdc-list': - 'dist/packages/material-experimental/mdc-list/index.js', - '@angular/material-experimental/mdc-menu': - 'dist/packages/material-experimental/mdc-menu/index.js', - '@angular/material-experimental/mdc-radio': - 'dist/packages/material-experimental/mdc-radio/index.js', - '@angular/material-experimental/mdc-snackbar': - 'dist/packages/material-experimental/mdc-snackbar/index.js', - '@angular/material-experimental/mdc-slide-toggle': - 'dist/packages/material-experimental/mdc-slide-toggle/index.js', - '@angular/material-experimental/mdc-slider': - 'dist/packages/material-experimental/mdc-slider/index.js', - '@angular/material-experimental/mdc-tabs': - 'dist/packages/material-experimental/mdc-tabs/index.js', - '@angular/material-experimental/mdc-table': - 'dist/packages/material-experimental/mdc-table/index.js', - '@angular/material-experimental/mdc-progress-bar': - 'dist/packages/material-experimental/mdc-progress-bar/index.js', - '@angular/material-experimental/popover-edit': - 'dist/packages/material-experimental/popover-edit/index.js', - '@angular/material/select/testing': - 'dist/packages/material/select/testing/index.js', - '@angular/material/select/testing/shared.spec': - 'dist/packages/material/select/testing/shared.spec.js', - }, - packages: { - // Thirdparty barrels. - 'rxjs': {main: 'index'}, - 'rxjs/operators': {main: 'index'}, - - // Needed for relative imports inside the testing package to work. - 'dist/packages/cdk/testing/testbed/fake-events': {main: 'index'}, - - // Set the default extension for the root package, because otherwise the tests can't - // be built within the production mode. Due to missing file extensions. - '.': {defaultExtension: 'js'} - } -}); diff --git a/test/karma.conf.js b/test/karma.conf.js index c5916ec76be8..3bdb13efd165 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -50,9 +50,15 @@ module.exports = config => { {pattern: 'node_modules/@angular/**/*', included: false, watched: false}, {pattern: 'node_modules/rxjs/**/*', included: false, watched: false}, - {pattern: 'test/karma-system-config.js', included: true, watched: false}, + // The Karma system configuration is built by Bazel. The built System config + // is copied into the "dist/" folder so that the Karma config can use it. + {pattern: 'dist/karma-system-config.js', included: true, watched: false}, {pattern: 'test/karma-test-shim.js', included: true, watched: false}, + // Needed for exposing the RxJS operators through the RxJS UMD bundle. This + // is done for performance reasons since fetching individual files is slow. + {pattern: 'tools/system-rxjs-operators.js', included: false, watched: false}, + // Include a Material theme in the test suite. Also include the MDC theme as // karma runs tests for the MDC prototype components as well. { diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index ce43ecceb383..d1b2f7158f51 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -1,5 +1,10 @@ package(default_visibility = ["//visibility:public"]) +exports_files([ + "system-config-tmpl.js", + "system-rxjs-operators.js", +]) + # Workaround for https://github.com/bazelbuild/bazel-toolchains/issues/356. We need the # "SYS_ADMIN" capability in order to run browsers with sandbox enabled. platform( diff --git a/tools/bazel/BUILD.bazel b/tools/bazel/BUILD.bazel index 5ec124b5ea2e..a9d978041d35 100644 --- a/tools/bazel/BUILD.bazel +++ b/tools/bazel/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") # Custom "tsc_wrapped" binary that has "tsickle" available as runtime dependency. # This is needed as the default compiler for a "ts_library" does not have a dependency diff --git a/tools/bazel/angular_bazel_rules_nodejs_1.0.0.patch b/tools/bazel/angular_bazel_rules_nodejs_1.0.0.patch new file mode 100644 index 000000000000..ae5ef4493b94 --- /dev/null +++ b/tools/bazel/angular_bazel_rules_nodejs_1.0.0.patch @@ -0,0 +1,77 @@ +diff --git node_modules/@angular/bazel/_BUILD.bazel node_modules/@angular/bazel/_BUILD.bazel +index 1e4880f..5654615 100755 +--- node_modules/@angular/bazel/_BUILD.bazel ++++ node_modules/@angular/bazel/_BUILD.bazel +@@ -1,6 +1,6 @@ +-load("//tools:defaults.bzl", "npm_package") ++load("//tools:defaults.bzl", "pkg_npm") + +-npm_package( ++pkg_npm( + name = "npm_package", + srcs = glob( + ["*"], +@@ -14,10 +14,10 @@ npm_package( + "//src/schematics:package_assets", + "//third_party/github.com/bazelbuild/bazel/src/main/protobuf:package_assets", + ], +- packages = [ ++ nested_packages = [ + "//docs", + ], +- replacements = { ++ substitutions = { + "(#|\/\/)\\s+BEGIN-DEV-ONLY[\\w\W]+?(#|\/\/)\\s+END-DEV-ONLY": "", + "//": "//", + "npm_angular_bazel/": "npm_angular_bazel/", +diff --git node_modules/@angular/bazel/src/ng_package/ng_package.bzl node_modules/@angular/bazel/src/ng_package/ng_package.bzl +index 55bd685..8aaefdc 100755 +--- node_modules/@angular/bazel/src/ng_package/ng_package.bzl ++++ node_modules/@angular/bazel/src/ng_package/ng_package.bzl +@@ -15,9 +15,9 @@ specification of this format at https://goo.gl/jB3GVv + + load("@build_bazel_rules_nodejs//:providers.bzl", "JSEcmaScriptModuleInfo", "JSNamedModuleInfo", "NpmPackageInfo", "node_modules_aspect") + load( +- "@build_bazel_rules_nodejs//internal/npm_package:npm_package.bzl", +- "NPM_PACKAGE_ATTRS", +- "NPM_PACKAGE_OUTPUTS", ++ "@build_bazel_rules_nodejs//internal/pkg_npm:pkg_npm.bzl", ++ "PKG_NPM_ATTRS", ++ "PKG_NPM_OUTPUTS", + "create_package", + ) + load("//src:external.bzl", "FLAT_DTS_FILE_SUFFIX") +@@ -623,7 +623,7 @@ def _ng_package_impl(ctx): + package_dir = create_package( + ctx, + devfiles.to_list(), +- [npm_package_directory] + ctx.files.packages, ++ [npm_package_directory] + ctx.files.nested_packages, + ) + return [DefaultInfo( + files = depset([package_dir]), +@@ -631,7 +631,7 @@ def _ng_package_impl(ctx): + + _NG_PACKAGE_DEPS_ASPECTS = [esm5_outputs_aspect, ng_package_module_mappings_aspect, node_modules_aspect] + +-_NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **{ ++_NG_PACKAGE_ATTRS = dict(PKG_NPM_ATTRS, **{ + "srcs": attr.label_list( + doc = """JavaScript source files from the workspace. + These can use ES2015 syntax and ES Modules (import/export)""", +@@ -807,12 +807,12 @@ def _ng_package_outputs(name, entry_point, entry_point_name): + "umd": "%s.umd.js" % basename, + "umd_min": "%s.umd.min.js" % basename, + } +- for key in NPM_PACKAGE_OUTPUTS: +- # NPM_PACKAGE_OUTPUTS is a "normal" dict-valued outputs so it looks like ++ for key in PKG_NPM_OUTPUTS: ++ # PKG_NPM_OUTPUTS is a "normal" dict-valued outputs so it looks like + # "pack": "%{name}.pack", + # But this is a function-valued outputs. + # Bazel won't replace the %{name} token so we have to do it. +- outputs[key] = NPM_PACKAGE_OUTPUTS[key].replace("%{name}", name) ++ outputs[key] = PKG_NPM_OUTPUTS[key].replace("%{name}", name) + return outputs + + ng_package = rule( diff --git a/tools/bazel/expand_template.bzl b/tools/bazel/expand_template.bzl index 78705b6cab03..c6a5642df92e 100644 --- a/tools/bazel/expand_template.bzl +++ b/tools/bazel/expand_template.bzl @@ -1,16 +1,16 @@ """Implementation of the expand_template rule """ def expand_template_impl(ctx): - replacements = dict(**ctx.attr.substitutions) + substitutions = dict(**ctx.attr.substitutions) for k in ctx.attr.configuration_env_vars: if k in ctx.var.keys(): - replacements["$%s_TMPL" % k.upper()] = ctx.var[k] + substitutions["$%s_TMPL" % k.upper()] = ctx.var[k] ctx.actions.expand_template( template = ctx.file.template, output = ctx.outputs.output_name, - substitutions = replacements, + substitutions = substitutions, ) """ diff --git a/tools/bazel/postinstall-patches.js b/tools/bazel/postinstall-patches.js index c259f2225294..77de9da082eb 100644 --- a/tools/bazel/postinstall-patches.js +++ b/tools/bazel/postinstall-patches.js @@ -9,10 +9,10 @@ const path = require('path'); const fs = require('fs'); /** - * Version of the post install patch. Needs to be incremented when patches - * have been added or removed. + * Version of the post install patch. Needs to be incremented when + * existing patches or edits have been modified. */ -const PATCH_VERSION = 1; +const PATCH_VERSION = 3; /** Path to the project directory. */ const projectDir = path.join(__dirname, '../..'); @@ -29,6 +29,14 @@ shelljs.cd(projectDir); // Workaround for https://github.com/angular/angular/issues/18810. shelljs.exec('ngc -p angular-tsconfig.json'); +try { + // Temporary patch to make @angular/bazel compatible with rules_nodejs 1.0.0. + // This is needed to resolve the dependency sandwich between angular components and + // repo framework. It can be removed with a future @angular/bazel update. + // try/catch needed for this the material CI tests to work in angular/repo + applyPatch(path.join(__dirname, './angular_bazel_rules_nodejs_1.0.0.patch')); +} catch (_) {} + // Workaround for https://github.com/angular/angular/issues/30586. It's not possible to // enable tsickle decorator processing without enabling import rewriting to closure. // This replacement allows us to enable decorator processing without rewriting imports. @@ -106,10 +114,7 @@ searchAndReplace(`[formatProperty + "_ivy_ngcc"]`, '[formatProperty]', // Workaround for https://github.com/angular/angular/issues/33452: searchAndReplace(/angular_compiler_options = {/, `$& - "strictTemplates": True, - "strictDomLocalRefTypes ": False, - "strictAttributeTypes": False, - "strictDomEventTypes": False,`, 'node_modules/@angular/bazel/src/ng_module.bzl'); + "strictTemplates": True,`, 'node_modules/@angular/bazel/src/ng_module.bzl'); // More info in https://github.com/angular/angular/pull/33786 shelljs.rm('-rf', [ diff --git a/tools/bazel/sass_worker_async.patch b/tools/bazel/sass_worker_async.patch new file mode 100644 index 000000000000..b79d73b19987 --- /dev/null +++ b/tools/bazel/sass_worker_async.patch @@ -0,0 +1,17 @@ +diff --git sass/sass_wrapper.js sass/sass_wrapper.js +index 21abb8f..9c750a3 100644 +--- sass/sass_wrapper.js ++++ sass/sass_wrapper.js +@@ -17,7 +17,11 @@ const fs = require('fs'); + const args = process.argv.slice(2); + if (runAsWorker(args)) { + debug('Starting Sass compiler persistent worker...'); +- runWorkerLoop(args => sass.run_(args)); ++ runWorkerLoop(args => { ++ return new Promise(resolve => { ++ sass.run_(args)['then$1$2$onError'](() => resolve(true), () => resolve(false)); ++ }); ++ }); + // Note: intentionally don't process.exit() here, because runWorkerLoop + // is waiting for async callbacks from node. + } else { diff --git a/tools/create-system-config.bzl b/tools/create-system-config.bzl new file mode 100644 index 000000000000..2f35cd80305b --- /dev/null +++ b/tools/create-system-config.bzl @@ -0,0 +1,42 @@ +load("//:packages.bzl", "ANGULAR_PACKAGE_BUNDLES") +load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS") +load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_ENTRYPOINTS") +load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS") +load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_ENTRYPOINTS") +load("//tools/bazel:expand_template.bzl", "expand_template") + +""" + Macro that builds a SystemJS configuration for all packages and entry-points + configured in "config.bzl" files of the workspace. The SystemJS configuration + can be used in the dev-app and for building the legacy unit tests SystemJS config. +""" + +def create_system_config( + name, + output_name, + # In Bazel the package output follows the same folder structure as the source + # code. This attribute makes the packages directory configurable since in the + # legacy output, the package output is located in "dist/packages". + packages_dir = "src", + # In Bazel, the node modules can be resolved without having to specify the + # path to the "node_modules" folder. In the legacy tests, this is not the case. + node_modules_base_path = "", + # In Bazel, files can be resolved without having to use Karma's default "base/" + # directory. For the legacy tests this needs to be configurable for now. + base_url = ""): + expand_template( + name = name, + output_name = output_name, + configuration_env_vars = ["angular_ivy_enabled"], + substitutions = { + "$ANGULAR_PACKAGE_BUNDLES": str(ANGULAR_PACKAGE_BUNDLES), + "$BASE_URL": base_url, + "$CDK_ENTRYPOINTS_TMPL": str(CDK_ENTRYPOINTS), + "$CDK_EXPERIMENTAL_ENTRYPOINTS_TMPL": str(CDK_EXPERIMENTAL_ENTRYPOINTS), + "$MATERIAL_ENTRYPOINTS_TMPL": str(MATERIAL_ENTRYPOINTS + MATERIAL_TESTING_ENTRYPOINTS), + "$MATERIAL_EXPERIMENTAL_ENTRYPOINTS_TMPL": str(MATERIAL_EXPERIMENTAL_ENTRYPOINTS), + "$NODE_MODULES_BASE_PATH": node_modules_base_path, + "$PACKAGES_DIR": packages_dir, + }, + template = "//tools:system-config-tmpl.js", + ) diff --git a/tools/defaults.bzl b/tools/defaults.bzl index ef32f6fa94e1..9c056bb11347 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -120,7 +120,7 @@ def ng_package(name, data = [], globals = ROLLUP_GLOBALS, readme_md = None, **kw globals = globals, data = data + [":license_copied"], readme_md = readme_md, - replacements = VERSION_PLACEHOLDER_REPLACEMENTS, + substitutions = VERSION_PLACEHOLDER_REPLACEMENTS, **kwargs ) @@ -175,10 +175,9 @@ def karma_web_test_suite(name, **kwargs): ) # Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1429 - native.sh_binary( + native.sh_test( name = "%s_local" % name, srcs = ["%s_local_bin" % name], - data = [":%s_local_bin" % name], tags = ["manual", "local", "ibazel_notify_changes"], testonly = True, ) @@ -243,6 +242,8 @@ def ng_web_test_suite(deps = [], static_css = [], bootstrap = [], tags = [], **k "//test:angular_test_init", ] + deps, browsers = [ + # Note: when changing the browser names here, also update the "yarn test" + # script to reflect the new browser names. "@io_bazel_rules_webtesting//browsers:chromium-local", "@io_bazel_rules_webtesting//browsers:firefox-local", ], diff --git a/tools/dev-server/BUILD.bazel b/tools/dev-server/BUILD.bazel index c7fd6fac8148..09aab2facf23 100644 --- a/tools/dev-server/BUILD.bazel +++ b/tools/dev-server/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") exports_files(["launcher_template.sh"]) diff --git a/tools/dev-server/dev-server.ts b/tools/dev-server/dev-server.ts index 2482c4a1c128..56ef222331b5 100644 --- a/tools/dev-server/dev-server.ts +++ b/tools/dev-server/dev-server.ts @@ -23,6 +23,7 @@ export class DevServer { /** Options of the browser-sync server. */ options: browserSync.Options = { open: false, + online: false, port: this.port, notify: false, ghostMode: false, diff --git a/tools/dgeni/BUILD.bazel b/tools/dgeni/BUILD.bazel index db94b1219427..64c24245a88e 100644 --- a/tools/dgeni/BUILD.bazel +++ b/tools/dgeni/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") nodejs_binary( diff --git a/tools/dgeni/common/class-inheritance.ts b/tools/dgeni/common/class-inheritance.ts index 20e91aa42f61..821090d79345 100644 --- a/tools/dgeni/common/class-inheritance.ts +++ b/tools/dgeni/common/class-inheritance.ts @@ -1,16 +1,80 @@ +// tslint:disable:no-bitwise + +import {ClassExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassExportDoc'; import {ClassLikeExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassLikeExportDoc'; +import {InterfaceExportDoc} from 'dgeni-packages/typescript/api-doc-types/InterfaceExportDoc'; +import * as ts from 'typescript'; /** Gets all class like export documents which the given doc inherits from. */ -export function getInheritedDocsOfClass(doc: ClassLikeExportDoc): ClassLikeExportDoc[] { - const directBaseDocs = [ - ...doc.implementsClauses.filter(clause => clause.doc).map(d => d.doc!), - ...doc.extendsClauses.filter(clause => clause.doc).map(d => d.doc!), - ]; +export function getInheritedDocsOfClass( + doc: ClassLikeExportDoc, + exportSymbolsToDocsMap: Map): ClassLikeExportDoc[] { + const result: ClassLikeExportDoc[] = []; + const typeChecker = doc.typeChecker; + for (let info of doc.extendsClauses) { + if (info.doc) { + result.push(info.doc, ...getInheritedDocsOfClass(info.doc, exportSymbolsToDocsMap)); + } else if (info.type) { + // If the heritage info has not been resolved to a Dgeni API document, we try to + // interpret the type expression and resolve/create corresponding Dgeni API documents. + // An example is the use of mixins. Type-wise mixins are not like real classes, because + // they are composed through an intersection type. In order to handle this pattern, we + // need to handle intersection types manually and resolve them to Dgeni API documents. + const resolvedType = typeChecker.getTypeAtLocation(info.type); + const docs = getClassLikeDocsFromType(resolvedType, doc, exportSymbolsToDocsMap); + // Add direct class-like types resolved from the expression. + result.push(...docs); + // Resolve inherited docs of the resolved documents. + docs.forEach(d => result.push(...getInheritedDocsOfClass(d, exportSymbolsToDocsMap))); + } + } + return result; +} + +/** + * Gets all class-like Dgeni documents from the given type. e.g. intersection types of + * multiple classes will result in multiple Dgeni API documents for each class. + */ +function getClassLikeDocsFromType( + type: ts.Type, baseDoc: ClassLikeExportDoc, + exportSymbolsToDocsMap: Map): ClassLikeExportDoc[] { + let aliasSymbol: ts.Symbol|undefined = undefined; + let symbol: ts.Symbol = type.symbol; + const typeChecker = baseDoc.typeChecker; + + // Symbols can be aliases of the declaration symbol. e.g. in named import + // specifiers. We need to resolve the aliased symbol back to the declaration symbol. + if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { + aliasSymbol = symbol; + symbol = typeChecker.getAliasedSymbol(symbol); + } - return [ - ...directBaseDocs, - // recursively collect base documents of direct base documents. - ...directBaseDocs.reduce( - (res: ClassLikeExportDoc[], d) => res.concat(getInheritedDocsOfClass(d)), []), - ]; + // Intersection types are commonly used in TypeScript mixins to express the + // class augmentation. e.g. "BaseClass & CanColor". + if (type.isIntersection()) { + return type.types.reduce( + (res, t) => [...res, ...getClassLikeDocsFromType(t, baseDoc, exportSymbolsToDocsMap)], + [] as ClassLikeExportDoc[]); + } else if (symbol) { + // If the given symbol has already been registered within Dgeni, we use the + // existing symbol instead of creating a new one. The dgeni typescript package + // keeps track of all exported symbols and their corresponding docs. See: + // dgeni-packages/blob/master/typescript/src/processors/linkInheritedDocs.ts + if (exportSymbolsToDocsMap.has(symbol)) { + return [exportSymbolsToDocsMap.get(symbol)!]; + } + let createdDoc: ClassLikeExportDoc|null = null; + if ((symbol.flags & ts.SymbolFlags.Class) !== 0) { + createdDoc = new ClassExportDoc(baseDoc.host, baseDoc.moduleDoc, symbol, aliasSymbol); + } else if ((symbol.flags & ts.SymbolFlags.Interface) !== 0) { + createdDoc = new InterfaceExportDoc(baseDoc.host, baseDoc.moduleDoc, symbol, aliasSymbol); + } + // If a new document has been created, add it to the shared symbol + // docs map and return it. + if (createdDoc) { + exportSymbolsToDocsMap.set(aliasSymbol || symbol, createdDoc); + return [createdDoc]; + } + } + return []; } diff --git a/tools/dgeni/docs-package.ts b/tools/dgeni/docs-package.ts index 44580323f2c4..0b9377b62e0d 100644 --- a/tools/dgeni/docs-package.ts +++ b/tools/dgeni/docs-package.ts @@ -1,15 +1,15 @@ import {Package} from 'dgeni'; +import {ReadTypeScriptModules} from 'dgeni-packages/typescript/processors/readTypeScriptModules'; import {Host} from 'dgeni-packages/typescript/services/ts-host/host'; +import {TypeFormatFlags} from 'typescript'; import {HighlightNunjucksExtension} from './nunjucks-tags/highlight'; import {patchLogService} from './patch-log-service'; import {AsyncFunctionsProcessor} from './processors/async-functions'; +import {categorizer} from './processors/categorizer'; import {DocsPrivateFilter} from './processors/docs-private-filter'; -import {Categorizer} from './processors/categorizer'; -import {FilterDuplicateExports} from './processors/filter-duplicate-exports'; -import {MergeInheritedProperties} from './processors/merge-inherited-properties'; import {EntryPointGrouper} from './processors/entry-point-grouper'; -import {ReadTypeScriptModules} from 'dgeni-packages/typescript/processors/readTypeScriptModules'; -import {TypeFormatFlags} from 'typescript'; +import {FilterDuplicateExports} from './processors/filter-duplicate-exports'; +import {mergeInheritedProperties} from './processors/merge-inherited-properties'; // Dgeni packages that the Material docs package depends on. const jsdocPackage = require('dgeni-packages/jsdoc'); @@ -39,13 +39,14 @@ export const apiDocsPackage = new Package('material2-api-docs', [ apiDocsPackage.processor(new FilterDuplicateExports()); // Processor that merges inherited properties of a class with the class doc. -apiDocsPackage.processor(new MergeInheritedProperties()); +// Note: needs to use a factory function since the processor relies on DI. +apiDocsPackage.processor(mergeInheritedProperties); // Processor that filters out symbols that should not be shown in the docs. apiDocsPackage.processor(new DocsPrivateFilter()); // Processor that appends categorization flags to the docs, e.g. `isDirective`, `isNgModule`, etc. -apiDocsPackage.processor(new Categorizer()); +apiDocsPackage.processor(categorizer); // Processor to group docs into top-level entry-points such as "tabs", "sidenav", etc. apiDocsPackage.processor(new EntryPointGrouper()); diff --git a/tools/dgeni/processors/categorizer.ts b/tools/dgeni/processors/categorizer.ts index a53014795892..adec260b52a6 100644 --- a/tools/dgeni/processors/categorizer.ts +++ b/tools/dgeni/processors/categorizer.ts @@ -1,5 +1,7 @@ import {DocCollection, Processor} from 'dgeni'; +import {ClassLikeExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassLikeExportDoc'; import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc'; +import * as ts from 'typescript'; import {getInheritedDocsOfClass} from '../common/class-inheritance'; import { decorateDeprecatedDoc, @@ -25,6 +27,14 @@ import {isPublicDoc} from '../common/private-docs'; import {getInputBindingData, getOutputBindingData} from '../common/property-bindings'; import {sortCategorizedMethodMembers, sortCategorizedPropertyMembers} from '../common/sort-members'; +/** + * Factory function for the "Categorizer" processor. Dgeni does not support + * dependency injection for classes. The symbol docs map is provided by the + * TypeScript dgeni package. + */ +export function categorizer(exportSymbolsToDocsMap: Map) { + return new Categorizer(exportSymbolsToDocsMap); +} /** * Processor to add properties to docs objects. @@ -35,9 +45,12 @@ import {sortCategorizedMethodMembers, sortCategorizedPropertyMembers} from '../c * isNgModule | Whether the doc is for an NgModule */ export class Categorizer implements Processor { - name = 'categorizer'; $runBefore = ['docs-processed', 'entryPointGrouper']; + constructor( + /** Shared map that can be used to resolve docs through symbols. */ + private _exportSymbolsToDocsMap: Map) {} + $process(docs: DocCollection) { docs.filter(doc => doc.docType === 'class' || doc.docType === 'interface') .forEach(doc => this._decorateClassLikeDoc(doc)); @@ -93,7 +106,7 @@ export class Categorizer implements Processor { // store the extended class in a variable. classDoc.extendedDoc = classDoc.extendsClauses[0] ? classDoc.extendsClauses[0].doc! : undefined; classDoc.directiveMetadata = getDirectiveMetadata(classDoc); - classDoc.inheritedDocs = getInheritedDocsOfClass(classDoc); + classDoc.inheritedDocs = getInheritedDocsOfClass(classDoc, this._exportSymbolsToDocsMap); // In case the extended document is not public, we don't want to print it in the // rendered class API doc. This causes confusion and also is not helpful as the diff --git a/tools/dgeni/processors/docs-private-filter.ts b/tools/dgeni/processors/docs-private-filter.ts index c8d00d716975..19de2d45bf86 100644 --- a/tools/dgeni/processors/docs-private-filter.ts +++ b/tools/dgeni/processors/docs-private-filter.ts @@ -9,7 +9,7 @@ import {getDocsPublicTag, isPublicDoc} from '../common/private-docs'; export class DocsPrivateFilter implements Processor { name = 'docs-private-filter'; $runBefore = ['categorizer']; - $runAfter = ['merge-inherited-properties']; + $runAfter = ['mergeInheritedProperties']; $process(docs: DocCollection) { return docs.filter(doc => { diff --git a/tools/dgeni/processors/merge-inherited-properties.ts b/tools/dgeni/processors/merge-inherited-properties.ts index 313b3f67e16a..e0ab4d13a21a 100644 --- a/tools/dgeni/processors/merge-inherited-properties.ts +++ b/tools/dgeni/processors/merge-inherited-properties.ts @@ -1,16 +1,31 @@ import {DocCollection, Processor} from 'dgeni'; import {ClassExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassExportDoc'; +import {ClassLikeExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassLikeExportDoc'; import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc'; +import * as ts from 'typescript'; import {getInheritedDocsOfClass} from '../common/class-inheritance'; +/** + * Factory function for the "MergeInheritedProperties" processor. Dgeni does not support + * dependency injection for classes. The symbol docs map is provided by the TypeScript + * dgeni package. + */ +export function mergeInheritedProperties( + exportSymbolsToDocsMap: Map) { + return new MergeInheritedProperties(exportSymbolsToDocsMap); +} + /** * Processor that merges inherited properties of a class with the class doc. This is necessary * to properly show public properties from TypeScript mixin interfaces in the API. */ export class MergeInheritedProperties implements Processor { - name = 'merge-inherited-properties'; $runBefore = ['categorizer']; + constructor( + /** Shared map that can be used to resolve docs through symbols. */ + private _exportSymbolsToDocsMap: Map) {} + $process(docs: DocCollection) { return docs.filter(doc => doc.docType === 'class') .forEach(doc => this._addInheritedProperties(doc)); @@ -19,7 +34,7 @@ export class MergeInheritedProperties implements Processor { private _addInheritedProperties(doc: ClassExportDoc) { // Note that we need to get check all base documents. We cannot assume // that directive base documents already have merged inherited members. - getInheritedDocsOfClass(doc).forEach(d => { + getInheritedDocsOfClass(doc, this._exportSymbolsToDocsMap).forEach(d => { d.members.forEach(member => { // only add inherited class members which are not "protected" or "private". if (member.accessibility === 'public') { @@ -35,9 +50,9 @@ export class MergeInheritedProperties implements Processor { // member doc for the destination class, we clone the member doc. It's important to keep // the prototype and reference because later, Dgeni identifies members and properties // by using an instance comparison. - const newMemberDoc = {...Object.create(memberDoc), ...memberDoc}; + // tslint:disable-next-line:ban Need to use Object.assign to preserve the prototype. + const newMemberDoc = Object.assign(Object.create(memberDoc), memberDoc); newMemberDoc.containerDoc = destination; - destination.members.push(newMemberDoc); } } diff --git a/tools/example-module/BUILD.bazel b/tools/example-module/BUILD.bazel index 2db324ba7545..750ac326c23a 100644 --- a/tools/example-module/BUILD.bazel +++ b/tools/example-module/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") ts_library( diff --git a/tools/example-module/bazel-bin.ts b/tools/example-module/bazel-bin.ts index 4ebcebe969ae..0c31b53141ab 100644 --- a/tools/example-module/bazel-bin.ts +++ b/tools/example-module/bazel-bin.ts @@ -9,6 +9,7 @@ if (require.main === module) { const [sourceFileManifest, outputFile, baseDir] = process.argv.slice(2); const sourceFiles = readFileSync(sourceFileManifest, 'utf8') .split(' ') + .map(filePath => filePath.trim()) .filter(s => s.endsWith('.ts')); generateExampleModule(sourceFiles, outputFile, baseDir); diff --git a/tools/example-module/example-module.template b/tools/example-module/example-module.template index 3ab96683ff33..1e2b43c0fae8 100644 --- a/tools/example-module/example-module.template +++ b/tools/example-module/example-module.template @@ -1,5 +1,3 @@ -/* tslint:disable */ - /** ****************************************************************************** * DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED. @@ -12,6 +10,7 @@ ******************************************************************************/ import {NgModule} from '@angular/core'; +import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field'; ${exampleImports} @@ -28,9 +27,14 @@ export const EXAMPLE_MODULES = ${exampleModules}; export const EXAMPLE_LIST = [${exampleList}]; +// Default MatFormField appearance to 'fill' as that is the new recommended approach and the +// `legacy` and `standard` appearances are scheduled for deprecation in version 10. @NgModule({ imports: EXAMPLE_MODULES, exports: EXAMPLE_MODULES, + providers: [ + { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'fill' } }, + ], }) export class ExampleModule { } diff --git a/tools/example-module/generate-example-module.ts b/tools/example-module/generate-example-module.ts index eec2445f68e5..855a450c5f25 100644 --- a/tools/example-module/generate-example-module.ts +++ b/tools/example-module/generate-example-module.ts @@ -41,7 +41,7 @@ function inlineExampleModuleTemplate(parsedData: AnalyzedExamples): string { .map(({additionalComponents, component, importPath}) => createImportDeclaration(importPath!, additionalComponents.concat(component))), ...exampleModules.map(({name, packagePath}) => createImportDeclaration(packagePath, [name])), - ].join(''); + ].join('\n'); const quotePlaceholder = '◬'; const exampleList = exampleMetadata.reduce((result, data) => { return result.concat(data.component).concat(data.additionalComponents); @@ -63,9 +63,9 @@ function inlineExampleModuleTemplate(parsedData: AnalyzedExamples): string { return fs.readFileSync(require.resolve('./example-module.template'), 'utf8') .replace(/\${exampleImports}/g, exampleImports) - .replace(/\${exampleComponents}/g, JSON.stringify(exampleComponents)) - .replace(/\${exampleList}/g, exampleList.join(', ')) - .replace(/\${exampleModules}/g, `[${exampleModules.map(m => m.name).join(', ')}]`) + .replace(/\${exampleComponents}/g, JSON.stringify(exampleComponents, null, 2)) + .replace(/\${exampleList}/g, exampleList.join(',\n')) + .replace(/\${exampleModules}/g, `[${exampleModules.map(m => m.name).join(',\n')}]`) .replace(new RegExp(`"${quotePlaceholder}|${quotePlaceholder}"`, 'g'), ''); } diff --git a/tools/gulp/packages.ts b/tools/gulp/packages.ts index 21aff85f8a06..bb38623decdc 100644 --- a/tools/gulp/packages.ts +++ b/tools/gulp/packages.ts @@ -8,14 +8,3 @@ export const cdkExperimentalPackage = new BuildPackage('cdk-experimental', [cdkP export const materialExperimentalPackage = new BuildPackage('material-experimental', [cdkPackage, cdkExperimentalPackage, materialPackage]); export const momentAdapterPackage = new BuildPackage('material-moment-adapter', [materialPackage]); - -/** List of all build packages defined for this project. */ -export const allBuildPackages = [ - cdkPackage, - materialPackage, - youTubePlayerPackage, - cdkExperimentalPackage, - materialExperimentalPackage, - momentAdapterPackage, - googleMapsPackage, -]; diff --git a/tools/gulp/tasks/unit-test.ts b/tools/gulp/tasks/unit-test.ts index 0bc541545002..8793f2daca4b 100644 --- a/tools/gulp/tasks/unit-test.ts +++ b/tools/gulp/tasks/unit-test.ts @@ -1,16 +1,9 @@ import {join} from 'path'; -import {task, watch} from 'gulp'; +import {task} from 'gulp'; import {buildConfig, sequenceTask} from '../../package-tools'; // There are no type definitions available for these imports. -const runSequence = require('run-sequence'); - -// Default Karma options. -const defaultOptions = { - configFile: join(buildConfig.projectDir, 'test/karma.conf.js'), - autoWatch: false, - singleRun: false -}; +const shelljs = require('shelljs'); /** Builds everything that is necessary for karma. */ task(':test:build', sequenceTask( @@ -22,6 +15,7 @@ task(':test:build', sequenceTask( 'youtube-player:build-no-bundles', 'material-moment-adapter:build-no-bundles', 'google-maps:build-no-bundles', + ':test:build-system-config' )); /** @@ -32,7 +26,11 @@ task('test:single-run', [':test:build'], (done: () => void) => { // Load karma not outside. Karma pollutes Promise with a different implementation. const karma = require('karma'); - new karma.Server({...defaultOptions, singleRun: true}, (exitCode: number) => { + new karma.Server({ + configFile: join(buildConfig.projectDir, 'test/karma.conf.js'), + autoWatch: false, + singleRun: true + }, (exitCode: number) => { // Immediately exit the process if Karma reported errors, because due to // potential still running tunnel-browsers gulp won't exit properly. exitCode === 0 ? done() : process.exit(exitCode); @@ -40,52 +38,14 @@ task('test:single-run', [':test:build'], (done: () => void) => { }); /** - * [Watch task] Runs the unit tests, rebuilding and re-testing when sources change. - * Does not inline resources. - * - * This task should be used when running unit tests locally. - */ -task('test', [':test:build'], karmaWatchTask()); - -/** - * Runs a Karma server which will run the unit tests against any browser that connects to it. - * This is identical to `gulp test`, however it won't launch and manage Chrome automatically, - * which makes it convenient debugging unit tests against multiple different browsers. - */ -task('test:static', [':test:build'], karmaWatchTask({browsers: []})); - -/** - * Returns a Gulp task that spawns a Karma server and reloads whenever the files change. - * Note that this doesn't use Karma's built-in file watching. Due to the way our build - * process is set up, Karma ends up firing its change detection for every file that is - * written to disk, which causes it to run tests multiple time and makes it hard to follow - * the console output. This approach runs the Karma server and then depends on the Gulp API - * to tell Karma when to run the tests. - * @param options Karma options to use on top of the defaults. + * Tasks that builds the SystemJS configuration which is needed for the Karma + * legacy unit tests. */ -function karmaWatchTask(options?: any) { - return () => { - const patternRoot = join(buildConfig.packagesDir, '**/*'); - // Note: Karma shouldn't be required from the outside, because it - // pollutes the global Promise with a custom implementation. - const karma = require('karma'); - - // Configure the Karma server and override the autoWatch and singleRun just in case. - const server = new karma.Server({...defaultOptions, ...options}); - - // Refreshes Karma's file list and schedules a test run. - // Tests will only run if TypeScript compilation was successful. - const runTests = (error?: Error) => { - if (!error) { - server.refreshFiles().then(() => server._injector.get('executor').schedule()); - } - }; - - // Boot up the test server and run the tests whenever a new browser connects. - server.start(); - server.on('browser_register', () => runTests()); - - // Whenever a file change has been recognized, rebuild and re-run the tests. - watch(patternRoot + '.+(ts|scss|html)', () => runSequence(':test:build', runTests)); - }; -} +task(':test:build-system-config', () => { + const configOutputPath = join(buildConfig.outputDir, 'karma-system-config.js'); + shelljs.cd(buildConfig.projectDir); + const bazelGenfilesDir = shelljs.exec('yarn -s bazel info bazel-genfiles').stdout.trim(); + shelljs.exec('yarn -s bazel build //test:system-config.js'); + shelljs.cp(join(bazelGenfilesDir, 'test/system-config.js'), configOutputPath); + shelljs.chmod('u+w', configOutputPath); +}); diff --git a/tools/highlight-files/BUILD.bazel b/tools/highlight-files/BUILD.bazel index 0013768109be..4ec6d91ebb9f 100644 --- a/tools/highlight-files/BUILD.bazel +++ b/tools/highlight-files/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") ts_library( diff --git a/tools/markdown-to-html/BUILD.bazel b/tools/markdown-to-html/BUILD.bazel index 841ff6d366ff..69c8aabc8300 100644 --- a/tools/markdown-to-html/BUILD.bazel +++ b/tools/markdown-to-html/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") ts_library( diff --git a/tools/package-docs-content/BUILD.bazel b/tools/package-docs-content/BUILD.bazel index 755c0cb64be2..85c8fdd7e843 100644 --- a/tools/package-docs-content/BUILD.bazel +++ b/tools/package-docs-content/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") load("//tools:defaults.bzl", "ts_library") nodejs_binary( diff --git a/tools/package-tools/gulp/build-scss-pipeline.ts b/tools/package-tools/gulp/build-scss-pipeline.ts index 396e64203e4e..107c51f9fcc1 100644 --- a/tools/package-tools/gulp/build-scss-pipeline.ts +++ b/tools/package-tools/gulp/build-scss-pipeline.ts @@ -4,13 +4,13 @@ import {buildConfig} from '../build-config'; // These imports lack of type definitions. const gulpSass = require('gulp-sass'); -const nodeSass = require('node-sass'); +const nodeSass = require('sass'); const sassIncludePaths = [ join(buildConfig.projectDir, 'node_modules/') ]; -// Set the compiler to our version of `node-sass`, rather than the one that `gulp-sass` depends on. +// Set the compiler to our version of `sass`, rather than the one that `gulp-sass` depends on. gulpSass.compiler = nodeSass; /** Create a gulp task that builds SCSS files. */ diff --git a/tools/public_api_guard/BUILD.bazel b/tools/public_api_guard/BUILD.bazel index 0e9432158110..63d80052b315 100644 --- a/tools/public_api_guard/BUILD.bazel +++ b/tools/public_api_guard/BUILD.bazel @@ -1,6 +1,17 @@ package(default_visibility = ["//visibility:public"]) +load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS") +load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS") load(":generate-guard-tests.bzl", "generate_test_targets") +golden_files = ["cdk/%s.d.ts" % e for e in CDK_ENTRYPOINTS] + \ + ["material/%s.d.ts" % e for e in MATERIAL_ENTRYPOINTS + MATERIAL_TESTING_ENTRYPOINTS] + [ + # Primary entry-points. + "cdk/cdk.d.ts", + "material/material.d.ts", + "youtube-player/youtube-player.d.ts", + "google-maps/google-maps.d.ts", +] + # Generate the API guard test targets for each golden file in the current package. -generate_test_targets(glob(["**/*.d.ts"])) +generate_test_targets(golden_files) diff --git a/tools/public_api_guard/cdk/a11y.d.ts b/tools/public_api_guard/cdk/a11y.d.ts index 90e6a05057f7..686cb37192ba 100644 --- a/tools/public_api_guard/cdk/a11y.d.ts +++ b/tools/public_api_guard/cdk/a11y.d.ts @@ -1,7 +1,7 @@ export declare class A11yModule { constructor(highContrastModeDetector: HighContrastModeDetector); static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class ActiveDescendantKeyManager extends ListKeyManager { @@ -25,10 +25,11 @@ export declare const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = "cdk-describedby-host"; export declare const CDK_DESCRIBEDBY_ID_PREFIX = "cdk-describedby-message"; export declare class CdkAriaLive implements OnDestroy { - politeness: AriaLivePoliteness; + get politeness(): AriaLivePoliteness; + set politeness(value: AriaLivePoliteness); constructor(_elementRef: ElementRef, _liveAnnouncer: LiveAnnouncer, _contentObserver: ContentObserver, _ngZone: NgZone); ngOnDestroy(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -36,13 +37,15 @@ export declare class CdkMonitorFocus implements OnDestroy { cdkFocusChange: EventEmitter; constructor(_elementRef: ElementRef, _focusMonitor: FocusMonitor); ngOnDestroy(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck { - autoCapture: boolean; - enabled: boolean; + get autoCapture(): boolean; + set autoCapture(value: boolean); + get enabled(): boolean; + set enabled(value: boolean); focusTrap: FocusTrap; constructor(_elementRef: ElementRef, _focusTrapFactory: FocusTrapFactory, _document: any); ngAfterContentInit(): void; @@ -50,10 +53,33 @@ export declare class CdkTrapFocus implements OnDestroy, AfterContentInit, DoChec ngOnDestroy(): void; static ngAcceptInputType_autoCapture: BooleanInput; static ngAcceptInputType_enabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } +export declare class ConfigurableFocusTrap extends FocusTrap implements ManagedFocusTrap { + get enabled(): boolean; + set enabled(value: boolean); + constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, _focusTrapManager: FocusTrapManager, _inertStrategy: FocusTrapInertStrategy, config: ConfigurableFocusTrapConfig); + _disable(): void; + _enable(): void; + destroy(): void; +} + +export declare class ConfigurableFocusTrapFactory { + constructor(_checker: InteractivityChecker, _ngZone: NgZone, _focusTrapManager: FocusTrapManager, _document: any, _inertStrategy?: FocusTrapInertStrategy); + create(element: HTMLElement, config?: ConfigurableFocusTrapConfig): ConfigurableFocusTrap; + static ɵfac: i0.ɵɵFactoryDef; + static ɵprov: i0.ɵɵInjectableDef; +} + +export declare class EventListenerFocusTrapInertStrategy implements FocusTrapInertStrategy { + allowFocus(focusTrap: ConfigurableFocusTrap): void; + preventFocus(focusTrap: ConfigurableFocusTrap): void; +} + +export declare const FOCUS_TRAP_INERT_STRATEGY: InjectionToken; + export interface FocusableOption extends ListKeyManagerOption { focus(origin?: FocusOrigin): void; } @@ -85,7 +111,12 @@ export interface FocusOptions { export declare type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null; export declare class FocusTrap { - enabled: boolean; + readonly _document: Document; + readonly _element: HTMLElement; + protected _enabled: boolean; + readonly _ngZone: NgZone; + get enabled(): boolean; + set enabled(value: boolean); protected endAnchorListener: () => boolean; protected startAnchorListener: () => boolean; constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, deferAnchors?: boolean); @@ -98,6 +129,7 @@ export declare class FocusTrap { focusLastTabbableElement(): boolean; focusLastTabbableElementWhenReady(): Promise; hasAttached(): boolean; + protected toggleAnchors(enabled: boolean): void; } export declare class FocusTrapFactory { @@ -107,6 +139,11 @@ export declare class FocusTrapFactory { static ɵprov: i0.ɵɵInjectableDef; } +export interface FocusTrapInertStrategy { + allowFocus(focusTrap: FocusTrap): void; + preventFocus(focusTrap: FocusTrap): void; +} + export declare const enum HighContrastMode { NONE = 0, BLACK_ON_WHITE = 1, @@ -139,8 +176,8 @@ export declare class InteractivityChecker { export declare function isFakeMousedownFromScreenReader(event: MouseEvent): boolean; export declare class ListKeyManager { - readonly activeItem: T | null; - readonly activeItemIndex: number | null; + get activeItem(): T | null; + get activeItemIndex(): number | null; change: Subject; tabOut: Subject; constructor(_items: QueryList | T[]); diff --git a/tools/public_api_guard/cdk/accordion.d.ts b/tools/public_api_guard/cdk/accordion.d.ts index fda468456b13..906efa6ac28b 100644 --- a/tools/public_api_guard/cdk/accordion.d.ts +++ b/tools/public_api_guard/cdk/accordion.d.ts @@ -2,13 +2,14 @@ export declare class CdkAccordion implements OnDestroy, OnChanges { readonly _openCloseAllActions: Subject; readonly _stateChanges: Subject; readonly id: string; - multi: boolean; + get multi(): boolean; + set multi(multi: boolean); closeAll(): void; ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; openAll(): void; static ngAcceptInputType_multi: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -17,8 +18,10 @@ export declare class CdkAccordionItem implements OnDestroy { accordion: CdkAccordion; closed: EventEmitter; destroyed: EventEmitter; - disabled: any; - expanded: any; + get disabled(): any; + set disabled(disabled: any); + get expanded(): any; + set expanded(expanded: any); expandedChange: EventEmitter; readonly id: string; opened: EventEmitter; @@ -29,7 +32,7 @@ export declare class CdkAccordionItem implements OnDestroy { toggle(): void; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_expanded: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/bidi.d.ts b/tools/public_api_guard/cdk/bidi.d.ts index e0bc461c3317..5dcf8514efa1 100644 --- a/tools/public_api_guard/cdk/bidi.d.ts +++ b/tools/public_api_guard/cdk/bidi.d.ts @@ -6,11 +6,12 @@ export declare class BidiModule { export declare class Dir implements Directionality, AfterContentInit, OnDestroy { _rawDir: string; change: EventEmitter; - dir: Direction; - readonly value: Direction; + get dir(): Direction; + set dir(value: Direction); + get value(): Direction; ngAfterContentInit(): void; ngOnDestroy(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/clipboard.d.ts b/tools/public_api_guard/cdk/clipboard.d.ts index 0373a94b92db..9e4b07f61f95 100644 --- a/tools/public_api_guard/cdk/clipboard.d.ts +++ b/tools/public_api_guard/cdk/clipboard.d.ts @@ -1,4 +1,4 @@ -export declare class CdkCopyToClipboard { +export declare class CdkCopyToClipboard implements OnDestroy { _deprecatedCopied: EventEmitter; attempts: number; copied: EventEmitter; @@ -6,7 +6,8 @@ export declare class CdkCopyToClipboard { constructor(_clipboard: Clipboard, _ngZone?: NgZone | undefined, config?: CdkCopyToClipboardConfig); copy(attempts?: number): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + ngOnDestroy(): void; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -26,7 +27,7 @@ export declare class Clipboard { export declare class ClipboardModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class PendingCopy { diff --git a/tools/public_api_guard/cdk/coercion.d.ts b/tools/public_api_guard/cdk/coercion.d.ts index 97604b4e9a9c..f1e266b511a4 100644 --- a/tools/public_api_guard/cdk/coercion.d.ts +++ b/tools/public_api_guard/cdk/coercion.d.ts @@ -13,6 +13,6 @@ export declare function coerceElement(elementOrRef: ElementRef | T): T; export declare function coerceNumberProperty(value: any): number; export declare function coerceNumberProperty(value: any, fallback: D): number | D; -export declare function coerceStringArray(value: any, separator: string | RegExp = /\s+/): string[]; +export declare function coerceStringArray(value: any, separator?: string | RegExp): string[]; export declare type NumberInput = string | number | null | undefined; diff --git a/tools/public_api_guard/cdk/collections.d.ts b/tools/public_api_guard/cdk/collections.d.ts index a2be3dc0853e..53359e1f0cac 100644 --- a/tools/public_api_guard/cdk/collections.d.ts +++ b/tools/public_api_guard/cdk/collections.d.ts @@ -30,7 +30,7 @@ export interface SelectionChange { export declare class SelectionModel { changed: Subject>; - readonly selected: T[]; + get selected(): T[]; constructor(_multiple?: boolean, initiallySelectedValues?: T[], _emitChanges?: boolean); clear(): void; deselect(...values: T[]): void; diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts index 6e4a7d7a6db3..13abcac7c21f 100644 --- a/tools/public_api_guard/cdk/drag-drop.d.ts +++ b/tools/public_api_guard/cdk/drag-drop.d.ts @@ -1,6 +1,6 @@ -export declare const CDK_DRAG_CONFIG: InjectionToken; +export declare const CDK_DRAG_CONFIG: InjectionToken; -export declare function CDK_DRAG_CONFIG_FACTORY(): DragRefConfig; +export declare function CDK_DRAG_CONFIG_FACTORY(): DragDropConfig; export declare const CDK_DROP_LIST: InjectionToken; @@ -12,11 +12,9 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes boundaryElement: string | ElementRef | HTMLElement; constrainPosition?: (point: Point, dragRef: DragRef) => Point; data: T; - disabled: boolean; - dragStartDelay: number | { - touch: number; - mouse: number; - }; + get disabled(): boolean; + set disabled(value: boolean); + dragStartDelay: DragStartDelay; dropContainer: CdkDropList; dropped: EventEmitter>; element: ElementRef; @@ -27,7 +25,7 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes x: number; y: number; }; - lockAxis: 'x' | 'y'; + lockAxis: DragAxis; moved: Observable>; previewClass: string | string[]; released: EventEmitter; @@ -35,7 +33,7 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes started: EventEmitter; constructor( element: ElementRef, - dropContainer: CdkDropList, _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragRefConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef); + dropContainer: CdkDropList, _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragDropConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef); getFreeDragPosition(): { readonly x: number; readonly y: number; @@ -47,7 +45,7 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes ngOnDestroy(): void; reset(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDrag]", ["cdkDrag"], { 'data': "cdkDragData", 'lockAxis': "cdkDragLockAxis", 'rootElementSelector': "cdkDragRootElement", 'boundaryElement': "cdkDragBoundary", 'dragStartDelay': "cdkDragStartDelay", 'freeDragPosition': "cdkDragFreeDragPosition", 'disabled': "cdkDragDisabled", 'constrainPosition': "cdkDragConstrainPosition", 'previewClass': "cdkDragPreviewClass" }, { 'started': "cdkDragStarted", 'released': "cdkDragReleased", 'ended': "cdkDragEnded", 'entered': "cdkDragEntered", 'exited': "cdkDragExited", 'dropped': "cdkDragDropped", 'moved': "cdkDragMoved" }, ["_previewTemplate", "_placeholderTemplate", "_handles"]>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDrag]", ["cdkDrag"], { "data": "cdkDragData"; "lockAxis": "cdkDragLockAxis"; "rootElementSelector": "cdkDragRootElement"; "boundaryElement": "cdkDragBoundary"; "dragStartDelay": "cdkDragStartDelay"; "freeDragPosition": "cdkDragFreeDragPosition"; "disabled": "cdkDragDisabled"; "constrainPosition": "cdkDragConstrainPosition"; "previewClass": "cdkDragPreviewClass"; }, { "started": "cdkDragStarted"; "released": "cdkDragReleased"; "ended": "cdkDragEnded"; "entered": "cdkDragEntered"; "exited": "cdkDragExited"; "dropped": "cdkDragDropped"; "moved": "cdkDragMoved"; }, ["_previewTemplate", "_placeholderTemplate", "_handles"]>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -86,12 +84,13 @@ export interface CdkDragExit { export declare class CdkDragHandle implements OnDestroy { _parentDrag: {} | undefined; _stateChanges: Subject; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); element: ElementRef; constructor(element: ElementRef, parentDrag?: any); ngOnDestroy(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -116,7 +115,7 @@ export declare class CdkDragPlaceholder { data: T; templateRef: TemplateRef; constructor(templateRef: TemplateRef); - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "ng-template[cdkDragPlaceholder]", never, { 'data': "data" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "ng-template[cdkDragPlaceholder]", never, { "data": "data"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -124,7 +123,7 @@ export declare class CdkDragPreview { data: T; templateRef: TemplateRef; constructor(templateRef: TemplateRef); - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "ng-template[cdkDragPreview]", never, { 'data': "data" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "ng-template[cdkDragPreview]", never, { "data": "data"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -149,19 +148,21 @@ export declare class CdkDropList implements AfterContentInit, OnDestroy autoScrollDisabled: boolean; connectedTo: (CdkDropList | string)[] | CdkDropList | string; data: T; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); dropped: EventEmitter>; element: ElementRef; enterPredicate: (drag: CdkDrag, drop: CdkDropList) => boolean; entered: EventEmitter>; exited: EventEmitter>; id: string; - lockAxis: 'x' | 'y'; - orientation: 'horizontal' | 'vertical'; + lockAxis: DragAxis; + orientation: DropListOrientation; sorted: EventEmitter>; sortingDisabled: boolean; constructor( - element: ElementRef, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup> | undefined); + element: ElementRef, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup> | undefined, + _scrollDispatcher?: ScrollDispatcher | undefined, config?: DragDropConfig); drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList, isPointerOverContainer: boolean): void; enter(item: CdkDrag, pointerX: number, pointerY: number): void; exit(item: CdkDrag): void; @@ -172,21 +173,26 @@ export declare class CdkDropList implements AfterContentInit, OnDestroy static ngAcceptInputType_autoScrollDisabled: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_sortingDisabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDropList], cdk-drop-list", ["cdkDropList"], { 'connectedTo': "cdkDropListConnectedTo", 'data': "cdkDropListData", 'orientation': "cdkDropListOrientation", 'id': "id", 'lockAxis': "cdkDropListLockAxis", 'disabled': "cdkDropListDisabled", 'sortingDisabled': "cdkDropListSortingDisabled", 'enterPredicate': "cdkDropListEnterPredicate", 'autoScrollDisabled': "cdkDropListAutoScrollDisabled" }, { 'dropped': "cdkDropListDropped", 'entered': "cdkDropListEntered", 'exited': "cdkDropListExited", 'sorted': "cdkDropListSorted" }, ["_draggables"]>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDropList], cdk-drop-list", ["cdkDropList"], { "connectedTo": "cdkDropListConnectedTo"; "data": "cdkDropListData"; "orientation": "cdkDropListOrientation"; "id": "id"; "lockAxis": "cdkDropListLockAxis"; "disabled": "cdkDropListDisabled"; "sortingDisabled": "cdkDropListSortingDisabled"; "enterPredicate": "cdkDropListEnterPredicate"; "autoScrollDisabled": "cdkDropListAutoScrollDisabled"; }, { "dropped": "cdkDropListDropped"; "entered": "cdkDropListEntered"; "exited": "cdkDropListExited"; "sorted": "cdkDropListSorted"; }, ["_draggables"]>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class CdkDropListGroup implements OnDestroy { readonly _items: Set; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); ngOnDestroy(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDropListGroup]", ["cdkDropListGroup"], { 'disabled': "cdkDropListGroupDisabled" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkDropListGroup]", ["cdkDropListGroup"], { "disabled": "cdkDropListGroupDisabled"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } export declare function copyArrayItem(currentArray: T[], targetArray: T[], currentIndex: number, targetIndex: number): void; +export declare type DragAxis = 'x' | 'y'; + +export declare type DragConstrainPosition = (point: Point, dragRef: DragRef) => Point; + export declare class DragDrop { constructor(_document: any, _ngZone: NgZone, _viewportRuler: ViewportRuler, _dragDropRegistry: DragDropRegistry); createDrag(element: ElementRef | HTMLElement, config?: DragRefConfig): DragRef; @@ -195,6 +201,19 @@ export declare class DragDrop { static ɵprov: i0.ɵɵInjectableDef; } +export interface DragDropConfig extends Partial { + boundaryElement?: string; + constrainPosition?: DragConstrainPosition; + dragStartDelay?: DragStartDelay; + draggingDisabled?: boolean; + listAutoScrollDisabled?: boolean; + listOrientation?: DropListOrientation; + lockAxis?: DragAxis; + previewClass?: string | string[]; + rootElementSelector?: string; + sortingDisabled?: boolean; +} + export declare class DragDropModule { static ɵinj: i0.ɵɵInjectorDef; static ɵmod: i0.ɵɵNgModuleDefWithMeta; @@ -221,7 +240,8 @@ export declare class DragRef { beforeStarted: Subject; constrainPosition?: (point: Point, dragRef: DragRef) => Point; data: T; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); dragStartDelay: number | { touch: number; mouse: number; @@ -294,6 +314,13 @@ export interface DragRefConfig { pointerDirectionChangeThreshold: number; } +export declare type DragStartDelay = number | { + touch: number; + mouse: number; +}; + +export declare type DropListOrientation = 'horizontal' | 'vertical'; + export declare class DropListRef { autoScrollDisabled: boolean; beforeStarted: Subject; @@ -351,6 +378,7 @@ export declare class DropListRef { withDirection(direction: Direction): this; withItems(items: DragRef[]): this; withOrientation(orientation: 'vertical' | 'horizontal'): this; + withScrollableParents(elements: HTMLElement[]): this; } export declare function moveItemInArray(array: T[], fromIndex: number, toIndex: number): void; diff --git a/tools/public_api_guard/cdk/observers.d.ts b/tools/public_api_guard/cdk/observers.d.ts index d32b4513f916..408f73f7273c 100644 --- a/tools/public_api_guard/cdk/observers.d.ts +++ b/tools/public_api_guard/cdk/observers.d.ts @@ -1,13 +1,15 @@ export declare class CdkObserveContent implements AfterContentInit, OnDestroy { - debounce: number; - disabled: any; + get debounce(): number; + set debounce(value: number); + get disabled(): any; + set disabled(value: any); event: EventEmitter; constructor(_contentObserver: ContentObserver, _elementRef: ElementRef, _ngZone: NgZone); ngAfterContentInit(): void; ngOnDestroy(): void; static ngAcceptInputType_debounce: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/overlay.d.ts b/tools/public_api_guard/cdk/overlay.d.ts index 355cc296e122..72e0b230e31c 100644 --- a/tools/public_api_guard/cdk/overlay.d.ts +++ b/tools/public_api_guard/cdk/overlay.d.ts @@ -10,25 +10,32 @@ export declare class CdkConnectedOverlay implements OnDestroy, OnChanges { backdropClass: string; backdropClick: EventEmitter; detach: EventEmitter; - readonly dir: Direction; - flexibleDimensions: boolean; - growAfterOpen: boolean; - hasBackdrop: any; + get dir(): Direction; + get flexibleDimensions(): boolean; + set flexibleDimensions(value: boolean); + get growAfterOpen(): boolean; + set growAfterOpen(value: boolean); + get hasBackdrop(): any; + set hasBackdrop(value: any); height: number | string; - lockPosition: any; + get lockPosition(): any; + set lockPosition(value: any); minHeight: number | string; minWidth: number | string; - offsetX: number; - offsetY: number; + get offsetX(): number; + set offsetX(offsetX: number); + get offsetY(): number; + set offsetY(offsetY: number); open: boolean; origin: CdkOverlayOrigin; overlayKeydown: EventEmitter; - readonly overlayRef: OverlayRef; + get overlayRef(): OverlayRef; panelClass: string | string[]; positionChange: EventEmitter; positionStrategy: FlexibleConnectedPositionStrategy; positions: ConnectedPosition[]; - push: boolean; + get push(): boolean; + set push(value: boolean); scrollStrategy: ScrollStrategy; transformOriginSelector: string; viewportMargin: number; @@ -41,7 +48,7 @@ export declare class CdkConnectedOverlay implements OnDestroy, OnChanges { static ngAcceptInputType_hasBackdrop: BooleanInput; static ngAcceptInputType_lockPosition: BooleanInput; static ngAcceptInputType_push: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -81,11 +88,11 @@ export interface ConnectedPosition { } export declare class ConnectedPositionStrategy implements PositionStrategy { - readonly _isRtl: boolean; + get _isRtl(): boolean; _positionStrategy: FlexibleConnectedPositionStrategy; _preferredPositions: ConnectionPositionPair[]; - readonly onPositionChange: Observable; - readonly positions: ConnectionPositionPair[]; + get onPositionChange(): Observable; + get positions(): ConnectionPositionPair[]; constructor(originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition, connectedTo: ElementRef, viewportRuler: ViewportRuler, document: Document, platform: Platform, overlayContainer: OverlayContainer); apply(): void; attach(overlayRef: OverlayReference): void; @@ -119,7 +126,7 @@ export declare class ConnectionPositionPair { export declare class FlexibleConnectedPositionStrategy implements PositionStrategy { _preferredPositions: ConnectionPositionPair[]; positionChanges: Observable; - readonly positions: ConnectionPositionPair[]; + get positions(): ConnectionPositionPair[]; constructor(connectedTo: FlexibleConnectedPositionStrategyOrigin, _viewportRuler: ViewportRuler, _document: Document, _platform: Platform, _overlayContainer: OverlayContainer); apply(): void; attach(overlayRef: OverlayReference): void; @@ -145,7 +152,8 @@ export declare type FlexibleConnectedPositionStrategyOrigin = ElementRef | HTMLE }; export declare class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy { - constructor(_document: any); + constructor(_document: any, + platform?: Platform); protected _createContainer(): void; getFullscreenElement(): Element; ngOnDestroy(): void; @@ -217,7 +225,9 @@ export interface OverlayConnectionPosition { export declare class OverlayContainer implements OnDestroy { protected _containerElement: HTMLElement; protected _document: Document; - constructor(document: any); + protected _platform?: Platform | undefined; + constructor(document: any, + _platform?: Platform | undefined); protected _createContainer(): void; getContainerElement(): HTMLElement; ngOnDestroy(): void; @@ -252,9 +262,9 @@ export declare class OverlayPositionBuilder { export declare class OverlayRef implements PortalOutlet, OverlayReference { _keydownEventSubscriptions: number; _keydownEvents: Subject; - readonly backdropElement: HTMLElement | null; - readonly hostElement: HTMLElement; - readonly overlayElement: HTMLElement; + get backdropElement(): HTMLElement | null; + get hostElement(): HTMLElement; + get overlayElement(): HTMLElement; constructor(_portalOutlet: PortalOutlet, _host: HTMLElement, _pane: HTMLElement, _config: ImmutableObject, _ngZone: NgZone, _keyboardDispatcher: OverlayKeyboardDispatcher, _document: Document, _location?: Location | undefined); addPanelClass(classes: string | string[]): void; attach(portal: ComponentPortal): ComponentRef; diff --git a/tools/public_api_guard/cdk/portal.d.ts b/tools/public_api_guard/cdk/portal.d.ts index 5b82e1467447..8dcdfe685edd 100644 --- a/tools/public_api_guard/cdk/portal.d.ts +++ b/tools/public_api_guard/cdk/portal.d.ts @@ -24,8 +24,9 @@ export declare class CdkPortal extends TemplatePortal { export declare class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestroy { attachDomPortal: (portal: DomPortal) => void; attached: EventEmitter; - readonly attachedRef: CdkPortalOutletAttachedRef; - portal: Portal | null; + get attachedRef(): CdkPortalOutletAttachedRef; + get portal(): Portal | null; + set portal(portal: Portal | null); constructor(_componentFactoryResolver: ComponentFactoryResolver, _viewContainerRef: ViewContainerRef, _document?: any); attachComponentPortal(portal: ComponentPortal): ComponentRef; @@ -33,7 +34,7 @@ export declare class CdkPortalOutlet extends BasePortalOutlet implements OnInit, ngOnDestroy(): void; ngOnInit(): void; static ngAcceptInputType_portal: Portal | null | undefined | ''; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -71,7 +72,7 @@ export declare class DomPortalOutlet extends BasePortalOutlet { } export declare abstract class Portal { - readonly isAttached: boolean; + get isAttached(): boolean; attach(host: PortalOutlet): T; detach(): void; setAttachedHost(host: PortalOutlet | null): void; @@ -80,7 +81,7 @@ export declare abstract class Portal { export declare type PortalHost = PortalOutlet; export declare class PortalHostDirective extends CdkPortalOutlet { - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -103,7 +104,7 @@ export interface PortalOutlet { export declare class TemplatePortal extends Portal> { context: C | undefined; - readonly origin: ElementRef; + get origin(): ElementRef; templateRef: TemplateRef; viewContainerRef: ViewContainerRef; constructor(template: TemplateRef, viewContainerRef: ViewContainerRef, context?: C); diff --git a/tools/public_api_guard/cdk/scrolling.d.ts b/tools/public_api_guard/cdk/scrolling.d.ts index aeae3e41e7dc..ef7b62d6c38a 100644 --- a/tools/public_api_guard/cdk/scrolling.d.ts +++ b/tools/public_api_guard/cdk/scrolling.d.ts @@ -39,14 +39,17 @@ export declare class CdkFixedSizeVirtualScroll implements OnChanges { _maxBufferPx: number; _minBufferPx: number; _scrollStrategy: FixedSizeVirtualScrollStrategy; - itemSize: number; - maxBufferPx: number; - minBufferPx: number; + get itemSize(): number; + set itemSize(value: number); + get maxBufferPx(): number; + set maxBufferPx(value: number); + get minBufferPx(): number; + set minBufferPx(value: number); ngOnChanges(): void; static ngAcceptInputType_itemSize: NumberInput; static ngAcceptInputType_maxBufferPx: NumberInput; static ngAcceptInputType_minBufferPx: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -68,10 +71,12 @@ export declare class CdkScrollable implements OnInit, OnDestroy { export declare class CdkVirtualForOf implements CollectionViewer, DoCheck, OnDestroy { _cdkVirtualForOf: DataSource | Observable | NgIterable | null | undefined; - cdkVirtualForOf: DataSource | Observable | NgIterable | null | undefined; - cdkVirtualForTemplate: TemplateRef>; + get cdkVirtualForOf(): DataSource | Observable | NgIterable | null | undefined; + set cdkVirtualForOf(value: DataSource | Observable | NgIterable | null | undefined); + set cdkVirtualForTemplate(value: TemplateRef>); cdkVirtualForTemplateCacheSize: number; - cdkVirtualForTrackBy: TrackByFunction | undefined; + get cdkVirtualForTrackBy(): TrackByFunction | undefined; + set cdkVirtualForTrackBy(fn: TrackByFunction | undefined); dataStream: Observable>; viewChange: Subject; constructor( @@ -82,7 +87,7 @@ export declare class CdkVirtualForOf implements CollectionViewer, DoCheck, On measureRangeSize(range: ListRange, orientation: 'horizontal' | 'vertical'): number; ngDoCheck(): void; ngOnDestroy(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkVirtualFor][cdkVirtualForOf]", never, { 'cdkVirtualForOf': "cdkVirtualForOf", 'cdkVirtualForTrackBy': "cdkVirtualForTrackBy", 'cdkVirtualForTemplate': "cdkVirtualForTemplate", 'cdkVirtualForTemplateCacheSize': "cdkVirtualForTemplateCacheSize" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkVirtualFor][cdkVirtualForOf]", never, { "cdkVirtualForOf": "cdkVirtualForOf"; "cdkVirtualForTrackBy": "cdkVirtualForTrackBy"; "cdkVirtualForTemplate": "cdkVirtualForTemplate"; "cdkVirtualForTemplateCacheSize": "cdkVirtualForTemplateCacheSize"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -102,10 +107,12 @@ export declare class CdkVirtualScrollViewport extends CdkScrollable implements O _totalContentHeight: string; _totalContentWidth: string; elementRef: ElementRef; - orientation: 'horizontal' | 'vertical'; + get orientation(): 'horizontal' | 'vertical'; + set orientation(orientation: 'horizontal' | 'vertical'); renderedRangeStream: Observable; scrolledIndexChange: Observable; - constructor(elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, ngZone: NgZone, _scrollStrategy: VirtualScrollStrategy, dir: Directionality, scrollDispatcher: ScrollDispatcher); + constructor(elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, ngZone: NgZone, _scrollStrategy: VirtualScrollStrategy, dir: Directionality, scrollDispatcher: ScrollDispatcher, + viewportRuler?: ViewportRuler); attach(forOf: CdkVirtualForOf): void; checkViewportSize(): void; detach(): void; @@ -123,7 +130,7 @@ export declare class CdkVirtualScrollViewport extends CdkScrollable implements O setRenderedContentOffset(offset: number, to?: 'to-start' | 'to-end'): void; setRenderedRange(range: ListRange): void; setTotalContentSize(size: number): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/stepper.d.ts b/tools/public_api_guard/cdk/stepper.d.ts index 8f3fc76ee38d..63f86bc30877 100644 --- a/tools/public_api_guard/cdk/stepper.d.ts +++ b/tools/public_api_guard/cdk/stepper.d.ts @@ -4,14 +4,18 @@ export declare class CdkStep implements OnChanges { _showError: boolean; ariaLabel: string; ariaLabelledby: string; - completed: boolean; + get completed(): boolean; + set completed(value: boolean); content: TemplateRef; - editable: boolean; + get editable(): boolean; + set editable(value: boolean); errorMessage: string; - hasError: boolean; + get hasError(): boolean; + set hasError(value: boolean); interacted: boolean; label: string; - optional: boolean; + get optional(): boolean; + set optional(value: boolean); state: StepState; stepControl: AbstractControlLike; stepLabel: CdkStepLabel; @@ -23,7 +27,7 @@ export declare class CdkStep implements OnChanges { static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; static ngAcceptInputType_optional: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -48,11 +52,14 @@ export declare class CdkStepper implements AfterViewInit, OnDestroy { protected _orientation: StepperOrientation; _stepHeader: QueryList; _steps: QueryList; - linear: boolean; - selected: CdkStep; - selectedIndex: number; + get linear(): boolean; + set linear(value: boolean); + get selected(): CdkStep; + set selected(step: CdkStep); + get selectedIndex(): number; + set selectedIndex(index: number); selectionChange: EventEmitter; - readonly steps: QueryList; + get steps(): QueryList; constructor(_dir: Directionality, _changeDetectorRef: ChangeDetectorRef, _elementRef?: ElementRef | undefined, _document?: any); _getAnimationDirection(index: number): StepContentPositionState; _getFocusIndex(): number | null; @@ -72,13 +79,13 @@ export declare class CdkStepper implements AfterViewInit, OnDestroy { static ngAcceptInputType_linear: BooleanInput; static ngAcceptInputType_optional: BooleanInput; static ngAcceptInputType_selectedIndex: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class CdkStepperModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class CdkStepperNext { @@ -86,7 +93,7 @@ export declare class CdkStepperNext { type: string; constructor(_stepper: CdkStepper); _handleClick(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -95,7 +102,7 @@ export declare class CdkStepperPrevious { type: string; constructor(_stepper: CdkStepper); _handleClick(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/table.d.ts b/tools/public_api_guard/cdk/table.d.ts index d6ba8c0ad4c9..b43da698f544 100644 --- a/tools/public_api_guard/cdk/table.d.ts +++ b/tools/public_api_guard/cdk/table.d.ts @@ -79,11 +79,13 @@ export declare class CdkColumnDef extends _CdkColumnDefBase implements CanStick cssClassFriendlyName: string; footerCell: CdkFooterCellDef; headerCell: CdkHeaderCellDef; - name: string; - stickyEnd: boolean; + get name(): string; + set name(name: string); + get stickyEnd(): boolean; + set stickyEnd(v: boolean); static ngAcceptInputType_sticky: BooleanInput; static ngAcceptInputType_stickyEnd: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -109,7 +111,7 @@ export declare class CdkFooterRowDef extends _CdkFooterRowDefBase implements Can constructor(template: TemplateRef, _differs: IterableDiffers); ngOnChanges(changes: SimpleChanges): void; static ngAcceptInputType_sticky: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -135,7 +137,7 @@ export declare class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements Can constructor(template: TemplateRef, _differs: IterableDiffers); ngOnChanges(changes: SimpleChanges): void; static ngAcceptInputType_sticky: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -147,7 +149,7 @@ export declare class CdkRow { export declare class CdkRowDef extends BaseRowDef { when: (index: number, rowData: T) => boolean; constructor(template: TemplateRef, _differs: IterableDiffers); - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkRowDef]", never, { 'columns': "cdkRowDefColumns", 'when': "cdkRowDefWhen" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkRowDef]", never, { "columns": "cdkRowDefColumns"; "when": "cdkRowDefWhen"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -165,10 +167,13 @@ export declare class CdkTable implements AfterContentChecked, CollectionViewe _headerRowOutlet: HeaderRowOutlet; _multiTemplateDataRows: boolean; _rowOutlet: DataRowOutlet; - dataSource: CdkTableDataSourceInput; - multiTemplateDataRows: boolean; + get dataSource(): CdkTableDataSourceInput; + set dataSource(dataSource: CdkTableDataSourceInput); + get multiTemplateDataRows(): boolean; + set multiTemplateDataRows(v: boolean); protected stickyCssClass: string; - trackBy: TrackByFunction; + get trackBy(): TrackByFunction; + set trackBy(fn: TrackByFunction); viewChange: BehaviorSubject<{ start: number; end: number; @@ -194,13 +199,13 @@ export declare class CdkTable implements AfterContentChecked, CollectionViewe updateStickyFooterRowStyles(): void; updateStickyHeaderRowStyles(): void; static ngAcceptInputType_multiTemplateDataRows: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-table, table[cdk-table]", ["cdkTable"], { 'trackBy': "trackBy", 'dataSource': "dataSource", 'multiTemplateDataRows': "multiTemplateDataRows" }, {}, ["_contentColumnDefs", "_contentRowDefs", "_contentHeaderRowDefs", "_contentFooterRowDefs"]>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-table, table[cdk-table]", ["cdkTable"], { "trackBy": "trackBy"; "dataSource": "dataSource"; "multiTemplateDataRows": "multiTemplateDataRows"; }, {}, ["_contentColumnDefs", "_contentRowDefs", "_contentHeaderRowDefs", "_contentFooterRowDefs"]>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class CdkTableModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class CdkTextColumn implements OnDestroy, OnInit { @@ -211,12 +216,13 @@ export declare class CdkTextColumn implements OnDestroy, OnInit { headerCell: CdkHeaderCellDef; headerText: string; justify: 'start' | 'end'; - name: string; + get name(): string; + set name(name: string); constructor(_table: CdkTable, _options: TextColumnOptions); _createDefaultHeaderText(): string; ngOnDestroy(): void; ngOnInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-text-column", never, { 'name': "name", 'headerText': "headerText", 'dataAccessor': "dataAccessor", 'justify': "justify" }, {}, never>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-text-column", never, { "name": "name"; "headerText": "headerText"; "dataAccessor": "dataAccessor"; "justify": "justify"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } diff --git a/tools/public_api_guard/cdk/testing.d.ts b/tools/public_api_guard/cdk/testing.d.ts index ad7a6a376f66..22ca48b285b4 100644 --- a/tools/public_api_guard/cdk/testing.d.ts +++ b/tools/public_api_guard/cdk/testing.d.ts @@ -115,6 +115,7 @@ export interface TestElement { getProperty(name: string): Promise; hasClass(name: string): Promise; hover(): Promise; + isFocused(): Promise; matchesSelector(selector: string): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; diff --git a/tools/public_api_guard/cdk/testing/protractor.d.ts b/tools/public_api_guard/cdk/testing/protractor.d.ts index e78e0132c2cc..a1e105b81cf0 100644 --- a/tools/public_api_guard/cdk/testing/protractor.d.ts +++ b/tools/public_api_guard/cdk/testing/protractor.d.ts @@ -11,6 +11,7 @@ export declare class ProtractorElement implements TestElement { getProperty(name: string): Promise; hasClass(name: string): Promise; hover(): Promise; + isFocused(): Promise; matchesSelector(selector: string): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; diff --git a/tools/public_api_guard/cdk/testing/testbed.d.ts b/tools/public_api_guard/cdk/testing/testbed.d.ts index 77aa55d72871..d2572ad5cf17 100644 --- a/tools/public_api_guard/cdk/testing/testbed.d.ts +++ b/tools/public_api_guard/cdk/testing/testbed.d.ts @@ -24,6 +24,7 @@ export declare class UnitTestElement implements TestElement { getProperty(name: string): Promise; hasClass(name: string): Promise; hover(): Promise; + isFocused(): Promise; matchesSelector(selector: string): Promise; sendKeys(...keys: (string | TestKey)[]): Promise; sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise; diff --git a/tools/public_api_guard/cdk/text-field.d.ts b/tools/public_api_guard/cdk/text-field.d.ts index e7b8253eaac7..6f8b80e3920d 100644 --- a/tools/public_api_guard/cdk/text-field.d.ts +++ b/tools/public_api_guard/cdk/text-field.d.ts @@ -19,14 +19,17 @@ export declare class CdkAutofill implements OnDestroy, OnInit { constructor(_elementRef: ElementRef, _autofillMonitor: AutofillMonitor); ngOnDestroy(): void; ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { - enabled: boolean; - maxRows: number; - minRows: number; + get enabled(): boolean; + set enabled(value: boolean); + get maxRows(): number; + set maxRows(value: number); + get minRows(): number; + set minRows(value: number); constructor(_elementRef: ElementRef, _platform: Platform, _ngZone: NgZone); _noopInputHandler(): void; _setMaxHeight(): void; @@ -39,7 +42,7 @@ export declare class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDe static ngAcceptInputType_enabled: BooleanInput; static ngAcceptInputType_maxRows: NumberInput; static ngAcceptInputType_minRows: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/cdk/tree.d.ts b/tools/public_api_guard/cdk/tree.d.ts index 4eaf846d99e5..c0e288279575 100644 --- a/tools/public_api_guard/cdk/tree.d.ts +++ b/tools/public_api_guard/cdk/tree.d.ts @@ -36,7 +36,8 @@ export declare class CdkNestedTreeNode extends CdkTreeNode implements Afte export declare class CdkTree implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit { _nodeDefs: QueryList>; _nodeOutlet: CdkTreeNodeOutlet; - dataSource: DataSource | Observable | T[]; + get dataSource(): DataSource | Observable | T[]; + set dataSource(dataSource: DataSource | Observable | T[]); trackBy: TrackByFunction; treeControl: TreeControl; viewChange: BehaviorSubject<{ @@ -50,13 +51,13 @@ export declare class CdkTree implements AfterContentChecked, CollectionViewer ngOnDestroy(): void; ngOnInit(): void; renderNodeChanges(data: T[] | ReadonlyArray, dataDiffer?: IterableDiffer, viewContainer?: ViewContainerRef, parentData?: T): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-tree", ["cdkTree"], { 'dataSource': "dataSource", 'treeControl': "treeControl", 'trackBy': "trackBy" }, {}, ["_nodeDefs"]>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "cdk-tree", ["cdkTree"], { "dataSource": "dataSource"; "treeControl": "treeControl"; "trackBy": "trackBy"; }, {}, ["_nodeDefs"]>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class CdkTreeModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class CdkTreeNode implements FocusableOption, OnDestroy { @@ -65,9 +66,10 @@ export declare class CdkTreeNode implements FocusableOption, OnDestroy { protected _destroyed: Subject; protected _elementRef: ElementRef; protected _tree: CdkTree; - data: T; - readonly isExpanded: boolean; - readonly level: number; + get data(): T; + set data(value: T); + get isExpanded(): boolean; + get level(): number; role: 'treeitem' | 'group'; constructor(_elementRef: ElementRef, _tree: CdkTree); protected _setRoleFromChildren(children: T[]): void; @@ -75,7 +77,7 @@ export declare class CdkTreeNode implements FocusableOption, OnDestroy { focus(): void; ngOnDestroy(): void; static mostRecentTreeNode: CdkTreeNode | null; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "cdk-tree-node", ["cdkTreeNode"], { 'role': "role" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "cdk-tree-node", ["cdkTreeNode"], { "role": "role"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -83,7 +85,7 @@ export declare class CdkTreeNodeDef { template: TemplateRef; when: (index: number, nodeData: T) => boolean; constructor(template: TemplateRef); - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodeDef]", never, { 'when': "cdkTreeNodeDefWhen" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodeDef]", never, { "when": "cdkTreeNodeDefWhen"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -106,15 +108,17 @@ export declare class CdkTreeNodeOutletContext { export declare class CdkTreeNodePadding implements OnDestroy { _indent: number; _level: number; - indent: number | string; + get indent(): number | string; + set indent(indent: number | string); indentUnits: string; - level: number; + get level(): number; + set level(value: number); constructor(_treeNode: CdkTreeNode, _tree: CdkTree, _renderer: Renderer2, _element: ElementRef, _dir: Directionality); _paddingIndent(): string | null; _setPadding(forceChange?: boolean): void; ngOnDestroy(): void; static ngAcceptInputType_level: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodePadding]", never, { 'level': "cdkTreeNodePadding", 'indent': "cdkTreeNodePaddingIndent" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodePadding]", never, { "level": "cdkTreeNodePadding"; "indent": "cdkTreeNodePaddingIndent"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -122,11 +126,12 @@ export declare class CdkTreeNodeToggle { protected _recursive: boolean; protected _tree: CdkTree; protected _treeNode: CdkTreeNode; - recursive: boolean; + get recursive(): boolean; + set recursive(value: boolean); constructor(_tree: CdkTree, _treeNode: CdkTreeNode); _toggle(event: Event): void; static ngAcceptInputType_recursive: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodeToggle]", never, { 'recursive': "cdkTreeNodeToggleRecursive" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[cdkTreeNodeToggle]", never, { "recursive": "cdkTreeNodeToggleRecursive"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } diff --git a/tools/public_api_guard/google-maps/google-maps.d.ts b/tools/public_api_guard/google-maps/google-maps.d.ts index 0c0e3427575b..275a793ef6d3 100644 --- a/tools/public_api_guard/google-maps/google-maps.d.ts +++ b/tools/public_api_guard/google-maps/google-maps.d.ts @@ -1,12 +1,13 @@ export declare class GoogleMap implements OnChanges, OnInit, OnDestroy { _googleMap: UpdatedGoogleMap; + _isBrowser: boolean; boundsChanged: Observable; - center: google.maps.LatLngLiteral | google.maps.LatLng; + set center(center: google.maps.LatLngLiteral | google.maps.LatLng); centerChanged: Observable; - readonly controls: Array>; - readonly data: google.maps.Data; + get controls(): Array>; + get data(): google.maps.Data; headingChanged: Observable; - height: string; + height: string | number; idle: Observable; mapClick: Observable; mapDblclick: Observable; @@ -17,17 +18,17 @@ export declare class GoogleMap implements OnChanges, OnInit, OnDestroy { mapMouseout: Observable; mapMouseover: Observable; mapRightclick: Observable; - readonly mapTypes: google.maps.MapTypeRegistry; + get mapTypes(): google.maps.MapTypeRegistry; maptypeidChanged: Observable; - options: google.maps.MapOptions; - readonly overlayMapTypes: google.maps.MVCArray; + set options(options: google.maps.MapOptions); + get overlayMapTypes(): google.maps.MVCArray; projectionChanged: Observable; tilesloaded: Observable; tiltChanged: Observable; - width: string; - zoom: number; + width: string | number; + set zoom(zoom: number); zoomChanged: Observable; - constructor(_elementRef: ElementRef, + constructor(_elementRef: ElementRef, _ngZone: NgZone, platformId?: Object); fitBounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral, padding?: number | google.maps.Padding): void; getBounds(): google.maps.LatLngBounds | null; @@ -45,7 +46,7 @@ export declare class GoogleMap implements OnChanges, OnInit, OnDestroy { panBy(x: number, y: number): void; panTo(latLng: google.maps.LatLng | google.maps.LatLngLiteral): void; panToBounds(latLngBounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral, padding?: number | google.maps.Padding): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -58,11 +59,11 @@ export declare class MapInfoWindow implements OnInit, OnDestroy { closeclick: Observable; contentChanged: Observable; domready: Observable; - options: google.maps.InfoWindowOptions; - position: google.maps.LatLngLiteral | google.maps.LatLng; + set options(options: google.maps.InfoWindowOptions); + set position(position: google.maps.LatLngLiteral | google.maps.LatLng); positionChanged: Observable; zindexChanged: Observable; - constructor(_googleMap: GoogleMap, _elementRef: ElementRef); + constructor(_googleMap: GoogleMap, _elementRef: ElementRef, _ngZone: NgZone); close(): void; getContent(): string | Node; getPosition(): google.maps.LatLng | null; @@ -70,20 +71,20 @@ export declare class MapInfoWindow implements OnInit, OnDestroy { ngOnDestroy(): void; ngOnInit(): void; open(anchor?: MapMarker): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MapMarker implements OnInit, OnDestroy { _marker?: google.maps.Marker; animationChanged: Observable; - clickable: boolean; + set clickable(clickable: boolean); clickableChanged: Observable; cursorChanged: Observable; draggableChanged: Observable; flatChanged: Observable; iconChanged: Observable; - label: string | google.maps.MarkerLabel; + set label(label: string | google.maps.MarkerLabel); mapClick: Observable; mapDblclick: Observable; mapDrag: Observable; @@ -94,15 +95,15 @@ export declare class MapMarker implements OnInit, OnDestroy { mapMouseover: Observable; mapMouseup: Observable; mapRightclick: Observable; - options: google.maps.MarkerOptions; - position: google.maps.LatLngLiteral | google.maps.LatLng; + set options(options: google.maps.MarkerOptions); + set position(position: google.maps.LatLngLiteral | google.maps.LatLng); positionChanged: Observable; shapeChanged: Observable; - title: string; + set title(title: string); titleChanged: Observable; visibleChanged: Observable; zindexChanged: Observable; - constructor(_googleMap: GoogleMap); + constructor(_googleMap: GoogleMap, _ngZone: NgZone); getAnimation(): google.maps.Animation | null; getClickable(): boolean; getCursor(): string | null; @@ -117,14 +118,14 @@ export declare class MapMarker implements OnInit, OnDestroy { getZIndex(): number | null; ngOnDestroy(): void; ngOnInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MapPolyline implements OnInit, OnDestroy { _polyline: google.maps.Polyline; - options: google.maps.PolylineOptions; - path: google.maps.MVCArray | google.maps.LatLng[] | google.maps.LatLngLiteral[]; + set options(options: google.maps.PolylineOptions); + set path(path: google.maps.MVCArray | google.maps.LatLng[] | google.maps.LatLngLiteral[]); polylineClick: Observable; polylineDblclick: Observable; polylineDrag: Observable; @@ -136,13 +137,13 @@ export declare class MapPolyline implements OnInit, OnDestroy { polylineMouseover: Observable; polylineMouseup: Observable; polylineRightclick: Observable; - constructor(_map: GoogleMap); + constructor(_map: GoogleMap, _ngZone: NgZone); getDraggable(): boolean; getEditable(): boolean; getPath(): google.maps.MVCArray; getVisible(): boolean; ngOnDestroy(): void; ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/autocomplete.d.ts b/tools/public_api_guard/material/autocomplete.d.ts index 41d87b3d709c..a9f9daaecbec 100644 --- a/tools/public_api_guard/material/autocomplete.d.ts +++ b/tools/public_api_guard/material/autocomplete.d.ts @@ -26,12 +26,13 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement }; _isOpen: boolean; _keyManager: ActiveDescendantKeyManager; - autoActiveFirstOption: boolean; - classList: string | string[]; + get autoActiveFirstOption(): boolean; + set autoActiveFirstOption(value: boolean); + set classList(value: string | string[]); readonly closed: EventEmitter; displayWith: ((value: any) => string) | null; id: string; - readonly isOpen: boolean; + get isOpen(): boolean; readonly opened: EventEmitter; optionGroups: QueryList; readonly optionSelected: EventEmitter; @@ -48,7 +49,7 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement ngAfterContentInit(): void; static ngAcceptInputType_autoActiveFirstOption: BooleanInput; static ngAcceptInputType_disableRipple: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -80,14 +81,15 @@ export declare class MatAutocompleteSelectedEvent { export declare class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy { _onChange: (value: any) => void; _onTouched: () => void; - readonly activeOption: MatOption | null; + get activeOption(): MatOption | null; autocomplete: MatAutocomplete; autocompleteAttribute: string; - autocompleteDisabled: boolean; + get autocompleteDisabled(): boolean; + set autocompleteDisabled(value: boolean); connectedTo: MatAutocompleteOrigin; readonly optionSelections: Observable; - readonly panelClosingActions: Observable; - readonly panelOpen: boolean; + get panelClosingActions(): Observable; + get panelOpen(): boolean; position: 'auto' | 'above' | 'below'; constructor(_element: ElementRef, _overlay: Overlay, _viewContainerRef: ViewContainerRef, _zone: NgZone, _changeDetectorRef: ChangeDetectorRef, scrollStrategy: any, _dir: Directionality, _formField: MatFormField, _document: any, _viewportRuler?: ViewportRuler | undefined); _handleFocus(): void; @@ -104,6 +106,6 @@ export declare class MatAutocompleteTrigger implements ControlValueAccessor, Aft updatePosition(): void; writeValue(value: any): void; static ngAcceptInputType_autocompleteDisabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/badge.d.ts b/tools/public_api_guard/material/badge.d.ts index d93670ada662..efc6e15bbebb 100644 --- a/tools/public_api_guard/material/badge.d.ts +++ b/tools/public_api_guard/material/badge.d.ts @@ -1,11 +1,15 @@ export declare class MatBadge extends _MatBadgeMixinBase implements OnDestroy, OnChanges, CanDisable { _hasContent: boolean; _id: number; - color: ThemePalette; + get color(): ThemePalette; + set color(value: ThemePalette); content: string; - description: string; - hidden: boolean; - overlap: boolean; + get description(): string; + set description(newDescription: string); + get hidden(): boolean; + set hidden(val: boolean); + get overlap(): boolean; + set overlap(val: boolean); position: MatBadgePosition; size: MatBadgeSize; constructor(_ngZone: NgZone, _elementRef: ElementRef, _ariaDescriber: AriaDescriber, _renderer: Renderer2, _animationMode?: string | undefined); @@ -17,7 +21,7 @@ export declare class MatBadge extends _MatBadgeMixinBase implements OnDestroy, O static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_hidden: BooleanInput; static ngAcceptInputType_overlap: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/bottom-sheet.d.ts b/tools/public_api_guard/material/bottom-sheet.d.ts index 5548da16cecc..6833a7f33aff 100644 --- a/tools/public_api_guard/material/bottom-sheet.d.ts +++ b/tools/public_api_guard/material/bottom-sheet.d.ts @@ -3,7 +3,8 @@ export declare const MAT_BOTTOM_SHEET_DATA: InjectionToken; export declare const MAT_BOTTOM_SHEET_DEFAULT_OPTIONS: InjectionToken>; export declare class MatBottomSheet implements OnDestroy { - _openedBottomSheetRef: MatBottomSheetRef | null; + get _openedBottomSheetRef(): MatBottomSheetRef | null; + set _openedBottomSheetRef(value: MatBottomSheetRef | null); constructor(_overlay: Overlay, _injector: Injector, _parentBottomSheet: MatBottomSheet, _location?: Location | undefined, _defaultOptions?: MatBottomSheetConfig | undefined); dismiss(): void; ngOnDestroy(): void; @@ -53,7 +54,7 @@ export declare class MatBottomSheetContainer extends BasePortalOutlet implements export declare class MatBottomSheetModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatBottomSheetRef { diff --git a/tools/public_api_guard/material/bottom-sheet/testing.d.ts b/tools/public_api_guard/material/bottom-sheet/testing.d.ts new file mode 100644 index 000000000000..62a6a1b2311c --- /dev/null +++ b/tools/public_api_guard/material/bottom-sheet/testing.d.ts @@ -0,0 +1,9 @@ +export interface BottomSheetHarnessFilters extends BaseHarnessFilters { +} + +export declare class MatBottomSheetHarness extends ComponentHarness { + dismiss(): Promise; + getAriaLabel(): Promise; + static hostSelector: string; + static with(options?: BottomSheetHarnessFilters): HarnessPredicate; +} diff --git a/tools/public_api_guard/material/button-toggle.d.ts b/tools/public_api_guard/material/button-toggle.d.ts index 13dd7b88c479..46a21017fe22 100644 --- a/tools/public_api_guard/material/button-toggle.d.ts +++ b/tools/public_api_guard/material/button-toggle.d.ts @@ -5,14 +5,17 @@ export declare const MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR: any; export declare class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit, CanDisableRipple, OnDestroy { _buttonElement: ElementRef; _type: ToggleType; - appearance: MatButtonToggleAppearance; + get appearance(): MatButtonToggleAppearance; + set appearance(value: MatButtonToggleAppearance); ariaLabel: string; ariaLabelledby: string | null; - readonly buttonId: string; + get buttonId(): string; buttonToggleGroup: MatButtonToggleGroup; readonly change: EventEmitter; - checked: boolean; - disabled: boolean; + get checked(): boolean; + set checked(value: boolean); + get disabled(): boolean; + set disabled(value: boolean); id: string; name: string; tabIndex: number | null; @@ -28,7 +31,7 @@ export declare class MatButtonToggle extends _MatButtonToggleMixinBase implement static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_multiple: BooleanInput; static ngAcceptInputType_vertical: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -52,13 +55,18 @@ export declare class MatButtonToggleGroup implements ControlValueAccessor, OnIni _onTouched: () => any; appearance: MatButtonToggleAppearance; readonly change: EventEmitter; - disabled: boolean; - multiple: boolean; - name: string; - readonly selected: MatButtonToggle | MatButtonToggle[]; - value: any; + get disabled(): boolean; + set disabled(value: boolean); + get multiple(): boolean; + set multiple(value: boolean); + get name(): string; + set name(value: string); + get selected(): MatButtonToggle | MatButtonToggle[]; + get value(): any; + set value(newValue: any); readonly valueChange: EventEmitter; - vertical: boolean; + get vertical(): boolean; + set vertical(value: boolean); constructor(_changeDetector: ChangeDetectorRef, defaultOptions?: MatButtonToggleDefaultOptions); _emitChangeEvent(): void; _isPrechecked(toggle: MatButtonToggle): boolean; @@ -73,7 +81,7 @@ export declare class MatButtonToggleGroup implements ControlValueAccessor, OnIni static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_multiple: BooleanInput; static ngAcceptInputType_vertical: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/button.d.ts b/tools/public_api_guard/material/button.d.ts index 9667e2d906a8..9a292e7f6f02 100644 --- a/tools/public_api_guard/material/button.d.ts +++ b/tools/public_api_guard/material/button.d.ts @@ -2,9 +2,7 @@ export declare class MatAnchor extends MatButton { tabIndex: number; constructor(focusMonitor: FocusMonitor, elementRef: ElementRef, animationMode: string); _haltDisabledEvents(event: Event): void; - static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -21,11 +19,11 @@ export declare class MatButton extends _MatButtonMixinBase implements OnDestroy, ngOnDestroy(): void; static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatButtonModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } diff --git a/tools/public_api_guard/material/card.d.ts b/tools/public_api_guard/material/card.d.ts index ceaca3a925b7..80f96f2e09e3 100644 --- a/tools/public_api_guard/material/card.d.ts +++ b/tools/public_api_guard/material/card.d.ts @@ -7,7 +7,7 @@ export declare class MatCard { export declare class MatCardActions { align: 'start' | 'end'; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/checkbox.d.ts b/tools/public_api_guard/material/checkbox.d.ts index 19907f69a774..d6e1e6128f10 100644 --- a/tools/public_api_guard/material/checkbox.d.ts +++ b/tools/public_api_guard/material/checkbox.d.ts @@ -20,15 +20,19 @@ export declare class MatCheckbox extends _MatCheckboxMixinBase implements Contro ariaLabel: string; ariaLabelledby: string | null; readonly change: EventEmitter; - checked: boolean; - disabled: any; + get checked(): boolean; + set checked(value: boolean); + get disabled(): any; + set disabled(value: any); id: string; - indeterminate: boolean; + get indeterminate(): boolean; + set indeterminate(value: boolean); readonly indeterminateChange: EventEmitter; - readonly inputId: string; + get inputId(): string; labelPosition: 'before' | 'after'; name: string | null; - required: boolean; + get required(): boolean; + set required(value: boolean); ripple: MatRipple; value: string; constructor(elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, _focusMonitor: FocusMonitor, _ngZone: NgZone, tabIndex: string, @@ -51,7 +55,7 @@ export declare class MatCheckbox extends _MatCheckboxMixinBase implements Contro static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_indeterminate: BooleanInput; static ngAcceptInputType_required: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -69,7 +73,7 @@ export interface MatCheckboxDefaultOptions { export declare class MatCheckboxModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatCheckboxRequiredValidator extends CheckboxRequiredValidator { diff --git a/tools/public_api_guard/material/chips.d.ts b/tools/public_api_guard/material/chips.d.ts index ee64561a08e7..bd91cc143ec4 100644 --- a/tools/public_api_guard/material/chips.d.ts +++ b/tools/public_api_guard/material/chips.d.ts @@ -1,6 +1,6 @@ export declare const MAT_CHIPS_DEFAULT_OPTIONS: InjectionToken; -export declare class MatChip extends _MatChipMixinBase implements FocusableOption, OnDestroy, CanColor, CanDisable, CanDisableRipple, RippleTarget { +export declare class MatChip extends _MatChipMixinBase implements FocusableOption, OnDestroy, CanColor, CanDisable, CanDisableRipple, RippleTarget, HasTabIndex { _animationsDisabled: boolean; _chipListMultiple: boolean; _elementRef: ElementRef; @@ -11,21 +11,25 @@ export declare class MatChip extends _MatChipMixinBase implements FocusableOptio protected _selectable: boolean; protected _selected: boolean; protected _value: any; - readonly ariaSelected: string | null; + get ariaSelected(): string | null; avatar: MatChipAvatar; chipListSelectable: boolean; readonly destroyed: EventEmitter; - removable: boolean; + get removable(): boolean; + set removable(value: boolean); removeIcon: MatChipRemove; readonly removed: EventEmitter; rippleConfig: RippleConfig & RippleGlobalOptions; - readonly rippleDisabled: boolean; - selectable: boolean; - selected: boolean; + get rippleDisabled(): boolean; + get selectable(): boolean; + set selectable(value: boolean); + get selected(): boolean; + set selected(value: boolean); readonly selectionChange: EventEmitter; trailingIcon: MatChipTrailingIcon; - value: any; - constructor(_elementRef: ElementRef, _ngZone: NgZone, platform: Platform, globalRippleOptions: RippleGlobalOptions | null, animationMode?: string, _changeDetectorRef?: ChangeDetectorRef | undefined); + get value(): any; + set value(value: any); + constructor(_elementRef: ElementRef, _ngZone: NgZone, platform: Platform, globalRippleOptions: RippleGlobalOptions | null, animationMode?: string, _changeDetectorRef?: ChangeDetectorRef | undefined, tabIndex?: string); _addHostClassName(): void; _blur(): void; _handleClick(event: Event): void; @@ -42,7 +46,7 @@ export declare class MatChip extends _MatChipMixinBase implements FocusableOptio static ngAcceptInputType_removable: BooleanInput; static ngAcceptInputType_selectable: BooleanInput; static ngAcceptInputType_selected: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -60,11 +64,13 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges { _chipList: MatChipList; protected _elementRef: ElementRef; protected _inputElement: HTMLInputElement; - addOnBlur: boolean; + get addOnBlur(): boolean; + set addOnBlur(value: boolean); chipEnd: EventEmitter; - chipList: MatChipList; - disabled: boolean; - readonly empty: boolean; + set chipList(value: MatChipList); + get disabled(): boolean; + set disabled(value: boolean); + get empty(): boolean; focused: boolean; id: string; placeholder: string; @@ -79,7 +85,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges { ngOnChanges(): void; static ngAcceptInputType_addOnBlur: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -106,28 +112,35 @@ export declare class MatChipList extends _MatChipListMixinBase implements MatFor protected _value: any; ariaOrientation: 'horizontal' | 'vertical'; readonly change: EventEmitter; - readonly chipBlurChanges: Observable; - readonly chipFocusChanges: Observable; - readonly chipRemoveChanges: Observable; - readonly chipSelectionChanges: Observable; + get chipBlurChanges(): Observable; + get chipFocusChanges(): Observable; + get chipRemoveChanges(): Observable; + get chipSelectionChanges(): Observable; chips: QueryList; - compareWith: (o1: any, o2: any) => boolean; + get compareWith(): (o1: any, o2: any) => boolean; + set compareWith(fn: (o1: any, o2: any) => boolean); readonly controlType: string; - disabled: boolean; - readonly empty: boolean; + get disabled(): boolean; + set disabled(value: boolean); + get empty(): boolean; errorStateMatcher: ErrorStateMatcher; - readonly focused: boolean; - readonly id: string; - multiple: boolean; + get focused(): boolean; + get id(): string; + get multiple(): boolean; + set multiple(value: boolean); ngControl: NgControl; - placeholder: string; - required: boolean; - readonly role: string | null; - selectable: boolean; - readonly selected: MatChip[] | MatChip; - readonly shouldLabelFloat: boolean; - tabIndex: number; - value: any; + get placeholder(): string; + set placeholder(value: string); + get required(): boolean; + set required(value: boolean); + get role(): string | null; + get selectable(): boolean; + set selectable(value: boolean); + get selected(): MatChip[] | MatChip; + get shouldLabelFloat(): boolean; + set tabIndex(value: number); + get value(): any; + set value(value: any); readonly valueChange: EventEmitter; constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _defaultErrorStateMatcher: ErrorStateMatcher, ngControl: NgControl); @@ -155,7 +168,7 @@ export declare class MatChipList extends _MatChipListMixinBase implements MatFor static ngAcceptInputType_multiple: BooleanInput; static ngAcceptInputType_required: BooleanInput; static ngAcceptInputType_selectable: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/core.d.ts b/tools/public_api_guard/material/core.d.ts index 4e39f0d4e2ee..cd8297ef2273 100644 --- a/tools/public_api_guard/material/core.d.ts +++ b/tools/public_api_guard/material/core.d.ts @@ -49,7 +49,7 @@ export declare type CanUpdateErrorStateCtor = Constructor; export declare abstract class DateAdapter { protected _localeChanges: Subject; protected locale: any; - readonly localeChanges: Observable; + get localeChanges(): Observable; abstract addCalendarDays(date: D, days: number): D; abstract addCalendarMonths(date: D, months: number): D; abstract addCalendarYears(date: D, years: number): D; @@ -243,22 +243,23 @@ export declare class MatOptgroup extends _MatOptgroupMixinBase implements CanDis _labelId: string; label: string; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatOption implements FocusableOption, AfterViewChecked, OnDestroy { readonly _stateChanges: Subject; - readonly active: boolean; - readonly disableRipple: boolean | undefined; - disabled: any; + get active(): boolean; + get disableRipple(): boolean | undefined; + get disabled(): any; + set disabled(value: any); readonly group: MatOptgroup; id: string; - readonly multiple: boolean | undefined; + get multiple(): boolean | undefined; readonly onSelectionChange: EventEmitter; - readonly selected: boolean; + get selected(): boolean; value: any; - readonly viewValue: string; + get viewValue(): string; constructor(_element: ElementRef, _changeDetectorRef: ChangeDetectorRef, _parent: MatOptionParentComponent, group: MatOptgroup); _getAriaSelected(): boolean | null; _getHostElement(): HTMLElement; @@ -274,7 +275,7 @@ export declare class MatOption implements FocusableOption, AfterViewChecked, OnD setActiveStyles(): void; setInactiveStyles(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -301,7 +302,7 @@ export declare class MatPseudoCheckbox { disabled: boolean; state: MatPseudoCheckboxState; constructor(_animationMode?: string | undefined); - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -316,11 +317,13 @@ export declare class MatRipple implements OnInit, OnDestroy, RippleTarget { animation: RippleAnimationConfig; centered: boolean; color: string; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); radius: number; - readonly rippleConfig: RippleConfig; - readonly rippleDisabled: boolean; - trigger: HTMLElement; + get rippleConfig(): RippleConfig; + get rippleDisabled(): boolean; + get trigger(): HTMLElement; + set trigger(trigger: HTMLElement); unbounded: boolean; constructor(_elementRef: ElementRef, ngZone: NgZone, platform: Platform, globalOptions?: RippleGlobalOptions, animationMode?: string); fadeOutAll(): void; @@ -328,7 +331,7 @@ export declare class MatRipple implements OnInit, OnDestroy, RippleTarget { launch(x: number, y: number, config?: RippleConfig): RippleRef; ngOnDestroy(): void; ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/datepicker.d.ts b/tools/public_api_guard/material/datepicker.d.ts index ab9d4defe69e..73ccfa9891a7 100644 --- a/tools/public_api_guard/material/datepicker.d.ts +++ b/tools/public_api_guard/material/datepicker.d.ts @@ -15,19 +15,25 @@ export declare const MAT_DATEPICKER_VALUE_ACCESSOR: any; export declare class MatCalendar implements AfterContentInit, AfterViewChecked, OnDestroy, OnChanges { _calendarHeaderPortal: Portal; readonly _userSelection: EventEmitter; - activeDate: D; - currentView: MatCalendarView; + get activeDate(): D; + set activeDate(value: D); + get currentView(): MatCalendarView; + set currentView(value: MatCalendarView); dateClass: (date: D) => MatCalendarCellCssClasses; dateFilter: (date: D) => boolean; headerComponent: ComponentType; - maxDate: D | null; - minDate: D | null; + get maxDate(): D | null; + set maxDate(value: D | null); + get minDate(): D | null; + set minDate(value: D | null); readonly monthSelected: EventEmitter; monthView: MatMonthView; multiYearView: MatMultiYearView; - selected: D | null; + get selected(): D | null; + set selected(value: D | null); readonly selectedChange: EventEmitter; - startAt: D | null; + get startAt(): D | null; + set startAt(value: D | null); startView: MatCalendarView; stateChanges: Subject; readonly yearSelected: EventEmitter; @@ -44,7 +50,7 @@ export declare class MatCalendar implements AfterContentInit, AfterViewChecke ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; updateTodaysDate(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-calendar", ["matCalendar"], { 'headerComponent': "headerComponent", 'startAt': "startAt", 'startView': "startView", 'selected': "selected", 'minDate': "minDate", 'maxDate': "maxDate", 'dateFilter': "dateFilter", 'dateClass': "dateClass" }, { 'selectedChange': "selectedChange", 'yearSelected': "yearSelected", 'monthSelected': "monthSelected", '_userSelection': "_userSelection" }, never>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-calendar", ["matCalendar"], { "headerComponent": "headerComponent"; "startAt": "startAt"; "startView": "startView"; "selected": "selected"; "minDate": "minDate"; "maxDate": "maxDate"; "dateFilter": "dateFilter"; "dateClass": "dateClass"; }, { "selectedChange": "selectedChange"; "yearSelected": "yearSelected"; "monthSelected": "monthSelected"; "_userSelection": "_userSelection"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -66,7 +72,7 @@ export declare class MatCalendarBody implements OnChanges { _focusActiveCell(): void; _isActiveCell(rowIndex: number, colIndex: number): boolean; ngOnChanges(changes: SimpleChanges): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -85,10 +91,10 @@ export declare type MatCalendarCellCssClasses = string | string[] | Set export declare class MatCalendarHeader { calendar: MatCalendar; - readonly nextButtonLabel: string; - readonly periodButtonLabel: string; - readonly periodButtonText: string; - readonly prevButtonLabel: string; + get nextButtonLabel(): string; + get periodButtonLabel(): string; + get periodButtonText(): string; + get prevButtonLabel(): string; constructor(_intl: MatDatepickerIntl, calendar: MatCalendar, _dateAdapter: DateAdapter, _dateFormats: MatDateFormats, changeDetectorRef: ChangeDetectorRef); currentPeriodClicked(): void; nextClicked(): void; @@ -103,27 +109,32 @@ export declare type MatCalendarView = 'month' | 'year' | 'multi-year'; export declare class MatDatepicker implements OnDestroy, CanColor { _color: ThemePalette; - readonly _dateFilter: (date: D | null) => boolean; + get _dateFilter(): (date: D | null) => boolean; _datepickerInput: MatDatepickerInput; readonly _disabledChange: Subject; - readonly _maxDate: D | null; - readonly _minDate: D | null; - _popupRef: OverlayRef; - _selected: D | null; + get _maxDate(): D | null; + get _minDate(): D | null; + get _selected(): D | null; + set _selected(value: D | null); readonly _selectedChanged: Subject; calendarHeaderComponent: ComponentType; closedStream: EventEmitter; - color: ThemePalette; + get color(): ThemePalette; + set color(value: ThemePalette); dateClass: (date: D) => MatCalendarCellCssClasses; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); id: string; readonly monthSelected: EventEmitter; - opened: boolean; + get opened(): boolean; + set opened(value: boolean); openedStream: EventEmitter; panelClass: string | string[]; - startAt: D | null; + get startAt(): D | null; + set startAt(value: D | null); startView: 'month' | 'year' | 'multi-year'; - touchUi: boolean; + get touchUi(): boolean; + set touchUi(value: boolean); readonly yearSelected: EventEmitter; constructor(_dialog: MatDialog, _overlay: Overlay, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, scrollStrategy: any, _dateAdapter: DateAdapter, _dir: Directionality, _document: any); _registerInput(input: MatDatepickerInput): void; @@ -135,7 +146,7 @@ export declare class MatDatepicker implements OnDestroy, CanColor { select(date: D): void; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_touchUi: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker", ["matDatepicker"], { 'calendarHeaderComponent': "calendarHeaderComponent", 'startAt': "startAt", 'startView': "startView", 'color': "color", 'touchUi': "touchUi", 'disabled': "disabled", 'panelClass': "panelClass", 'dateClass': "dateClass", 'opened': "opened" }, { 'yearSelected': "yearSelected", 'monthSelected': "monthSelected", 'openedStream': "opened", 'closedStream': "closed" }, never>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker", ["matDatepicker"], { "calendarHeaderComponent": "calendarHeaderComponent"; "startAt": "startAt"; "startView": "startView"; "color": "color"; "touchUi": "touchUi"; "disabled": "disabled"; "panelClass": "panelClass"; "dateClass": "dateClass"; "opened": "opened"; }, { "yearSelected": "yearSelected"; "monthSelected": "monthSelected"; "openedStream": "opened"; "closedStream": "closed"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -144,13 +155,18 @@ export declare const matDatepickerAnimations: { readonly fadeInCalendar: AnimationTriggerMetadata; }; -export declare class MatDatepickerContent extends _MatDatepickerContentMixinBase implements AfterViewInit, CanColor { +export declare class MatDatepickerContent extends _MatDatepickerContentMixinBase implements AfterViewInit, OnDestroy, CanColor { + _animationDone: Subject; + _animationState: 'enter' | 'void'; _calendar: MatCalendar; _isAbove: boolean; datepicker: MatDatepicker; - constructor(elementRef: ElementRef); + constructor(elementRef: ElementRef, + _changeDetectorRef?: ChangeDetectorRef | undefined); + _startExitAnimation(): void; ngAfterViewInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker-content", ["matDatepickerContent"], { 'color': "color" }, {}, never>; + ngOnDestroy(): void; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker-content", ["matDatepickerContent"], { "color": "color"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -163,12 +179,16 @@ export declare class MatDatepickerInput implements ControlValueAccessor, OnDe _valueChange: EventEmitter; readonly dateChange: EventEmitter>; readonly dateInput: EventEmitter>; - disabled: boolean; - matDatepicker: MatDatepicker; - matDatepickerFilter: (date: D | null) => boolean; - max: D | null; - min: D | null; - value: D | null; + get disabled(): boolean; + set disabled(value: boolean); + set matDatepicker(value: MatDatepicker); + set matDatepickerFilter(value: (date: D | null) => boolean); + get max(): D | null; + set max(value: D | null); + get min(): D | null; + set min(value: D | null); + get value(): D | null; + set value(value: D | null); constructor(_elementRef: ElementRef, _dateAdapter: DateAdapter, _dateFormats: MatDateFormats, _formField: MatFormField); _getThemePalette(): ThemePalette; _onBlur(): void; @@ -186,7 +206,7 @@ export declare class MatDatepickerInput implements ControlValueAccessor, OnDe writeValue(value: D): void; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_value: any; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "input[matDatepicker]", ["matDatepickerInput"], { 'matDatepicker': "matDatepicker", 'matDatepickerFilter': "matDatepickerFilter", 'value': "value", 'min': "min", 'max': "max", 'disabled': "disabled" }, { 'dateChange': "dateChange", 'dateInput': "dateInput" }, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "input[matDatepicker]", ["matDatepickerInput"], { "matDatepicker": "matDatepicker"; "matDatepickerFilter": "matDatepickerFilter"; "value": "value"; "min": "min"; "max": "max"; "disabled": "disabled"; }, { "dateChange": "dateChange"; "dateInput": "dateInput"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -227,7 +247,8 @@ export declare class MatDatepickerToggle implements AfterContentInit, OnChang _intl: MatDatepickerIntl; datepicker: MatDatepicker; disableRipple: boolean; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); tabIndex: number | null; constructor(_intl: MatDatepickerIntl, _changeDetectorRef: ChangeDetectorRef, defaultTabIndex: string); _open(event: Event): void; @@ -235,7 +256,7 @@ export declare class MatDatepickerToggle implements AfterContentInit, OnChang ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker-toggle", ["matDatepickerToggle"], { 'datepicker': "for", 'tabIndex': "tabIndex", 'disabled': "disabled", 'disableRipple': "disableRipple" }, {}, ["_customIcon"]>; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-datepicker-toggle", ["matDatepickerToggle"], { "datepicker": "for"; "tabIndex": "tabIndex"; "disabled": "disabled"; "disableRipple": "disableRipple"; }, {}, ["_customIcon"]>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -244,7 +265,7 @@ export declare class MatDatepickerToggleIcon { static ɵfac: i0.ɵɵFactoryDef; } -export declare class MatMonthView implements AfterContentInit { +export declare class MatMonthView implements AfterContentInit, OnDestroy { _dateAdapter: DateAdapter; _firstWeekOffset: number; _matCalendarBody: MatCalendarBody; @@ -257,13 +278,17 @@ export declare class MatMonthView implements AfterContentInit { narrow: string; }[]; _weeks: MatCalendarCell[][]; - activeDate: D; + get activeDate(): D; + set activeDate(value: D); readonly activeDateChange: EventEmitter; dateClass: (date: D) => MatCalendarCellCssClasses; dateFilter: (date: D) => boolean; - maxDate: D | null; - minDate: D | null; - selected: D | null; + get maxDate(): D | null; + set maxDate(value: D | null); + get minDate(): D | null; + set minDate(value: D | null); + get selected(): D | null; + set selected(value: D | null); readonly selectedChange: EventEmitter; constructor(_changeDetectorRef: ChangeDetectorRef, _dateFormats: MatDateFormats, _dateAdapter: DateAdapter, _dir?: Directionality | undefined); _dateSelected(date: number): void; @@ -271,22 +296,27 @@ export declare class MatMonthView implements AfterContentInit { _handleCalendarBodyKeydown(event: KeyboardEvent): void; _init(): void; ngAfterContentInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-month-view", ["matMonthView"], { 'activeDate': "activeDate", 'selected': "selected", 'minDate': "minDate", 'maxDate': "maxDate", 'dateFilter': "dateFilter", 'dateClass': "dateClass" }, { 'selectedChange': "selectedChange", '_userSelection': "_userSelection", 'activeDateChange': "activeDateChange" }, never>; + ngOnDestroy(): void; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-month-view", ["matMonthView"], { "activeDate": "activeDate"; "selected": "selected"; "minDate": "minDate"; "maxDate": "maxDate"; "dateFilter": "dateFilter"; "dateClass": "dateClass"; }, { "selectedChange": "selectedChange"; "_userSelection": "_userSelection"; "activeDateChange": "activeDateChange"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } -export declare class MatMultiYearView implements AfterContentInit { +export declare class MatMultiYearView implements AfterContentInit, OnDestroy { _dateAdapter: DateAdapter; _matCalendarBody: MatCalendarBody; _selectedYear: number | null; _todayYear: number; _years: MatCalendarCell[][]; - activeDate: D; + get activeDate(): D; + set activeDate(value: D); readonly activeDateChange: EventEmitter; dateFilter: (date: D) => boolean; - maxDate: D | null; - minDate: D | null; - selected: D | null; + get maxDate(): D | null; + set maxDate(value: D | null); + get minDate(): D | null; + set minDate(value: D | null); + get selected(): D | null; + set selected(value: D | null); readonly selectedChange: EventEmitter; readonly yearSelected: EventEmitter; constructor(_changeDetectorRef: ChangeDetectorRef, _dateAdapter: DateAdapter, _dir?: Directionality | undefined); @@ -296,24 +326,29 @@ export declare class MatMultiYearView implements AfterContentInit { _init(): void; _yearSelected(year: number): void; ngAfterContentInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-multi-year-view", ["matMultiYearView"], { 'activeDate': "activeDate", 'selected': "selected", 'minDate': "minDate", 'maxDate': "maxDate", 'dateFilter': "dateFilter" }, { 'selectedChange': "selectedChange", 'yearSelected': "yearSelected", 'activeDateChange': "activeDateChange" }, never>; + ngOnDestroy(): void; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-multi-year-view", ["matMultiYearView"], { "activeDate": "activeDate"; "selected": "selected"; "minDate": "minDate"; "maxDate": "maxDate"; "dateFilter": "dateFilter"; }, { "selectedChange": "selectedChange"; "yearSelected": "yearSelected"; "activeDateChange": "activeDateChange"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } -export declare class MatYearView implements AfterContentInit { +export declare class MatYearView implements AfterContentInit, OnDestroy { _dateAdapter: DateAdapter; _matCalendarBody: MatCalendarBody; _months: MatCalendarCell[][]; _selectedMonth: number | null; _todayMonth: number | null; _yearLabel: string; - activeDate: D; + get activeDate(): D; + set activeDate(value: D); readonly activeDateChange: EventEmitter; dateFilter: (date: D) => boolean; - maxDate: D | null; - minDate: D | null; + get maxDate(): D | null; + set maxDate(value: D | null); + get minDate(): D | null; + set minDate(value: D | null); readonly monthSelected: EventEmitter; - selected: D | null; + get selected(): D | null; + set selected(value: D | null); readonly selectedChange: EventEmitter; constructor(_changeDetectorRef: ChangeDetectorRef, _dateFormats: MatDateFormats, _dateAdapter: DateAdapter, _dir?: Directionality | undefined); _focusActiveCell(): void; @@ -321,7 +356,8 @@ export declare class MatYearView implements AfterContentInit { _init(): void; _monthSelected(month: number): void; ngAfterContentInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-year-view", ["matYearView"], { 'activeDate': "activeDate", 'selected': "selected", 'minDate': "minDate", 'maxDate': "maxDate", 'dateFilter': "dateFilter" }, { 'selectedChange': "selectedChange", 'monthSelected': "monthSelected", 'activeDateChange': "activeDateChange" }, never>; + ngOnDestroy(): void; + static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-year-view", ["matYearView"], { "activeDate": "activeDate"; "selected": "selected"; "minDate": "minDate"; "maxDate": "maxDate"; "dateFilter": "dateFilter"; }, { "selectedChange": "selectedChange"; "monthSelected": "monthSelected"; "activeDateChange": "activeDateChange"; }, never>; static ɵfac: i0.ɵɵFactoryDef>; } diff --git a/tools/public_api_guard/material/dialog.d.ts b/tools/public_api_guard/material/dialog.d.ts index 9eb00c988442..6b0eaf3f5228 100644 --- a/tools/public_api_guard/material/dialog.d.ts +++ b/tools/public_api_guard/material/dialog.d.ts @@ -24,10 +24,10 @@ export declare const MAT_DIALOG_SCROLL_STRATEGY_PROVIDER: { export declare function MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay): () => ScrollStrategy; export declare class MatDialog implements OnDestroy { - readonly _afterAllClosed: Subject; + get _afterAllClosed(): Subject; readonly afterAllClosed: Observable; - readonly afterOpened: Subject>; - readonly openDialogs: MatDialogRef[]; + get afterOpened(): Subject>; + get openDialogs(): MatDialogRef[]; constructor(_overlay: Overlay, _injector: Injector, _location: Location, _defaultOptions: MatDialogConfig, scrollStrategy: any, _parentDialog: MatDialog, _overlayContainer: OverlayContainer); closeAll(): void; @@ -56,7 +56,7 @@ export declare class MatDialogClose implements OnInit, OnChanges { constructor(dialogRef: MatDialogRef, _elementRef: ElementRef, _dialog: MatDialog); ngOnChanges(changes: SimpleChanges): void; ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -113,7 +113,7 @@ export declare class MatDialogContent { export declare class MatDialogModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatDialogRef { @@ -145,7 +145,7 @@ export declare class MatDialogTitle implements OnInit { id: string; constructor(_dialogRef: MatDialogRef, _elementRef: ElementRef, _dialog: MatDialog); ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/divider.d.ts b/tools/public_api_guard/material/divider.d.ts index f0c3ef1e5d1d..29c24e80a8df 100644 --- a/tools/public_api_guard/material/divider.d.ts +++ b/tools/public_api_guard/material/divider.d.ts @@ -1,13 +1,15 @@ export declare class MatDivider { - inset: boolean; - vertical: boolean; + get inset(): boolean; + set inset(value: boolean); + get vertical(): boolean; + set vertical(value: boolean); static ngAcceptInputType_inset: BooleanInput; static ngAcceptInputType_vertical: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatDividerModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } diff --git a/tools/public_api_guard/material/expansion.d.ts b/tools/public_api_guard/material/expansion.d.ts index 92d902169a53..a6e20a59db02 100644 --- a/tools/public_api_guard/material/expansion.d.ts +++ b/tools/public_api_guard/material/expansion.d.ts @@ -7,14 +7,14 @@ export declare const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS: InjectionToken; displayMode: MatAccordionDisplayMode; - hideToggle: boolean; + get hideToggle(): boolean; + set hideToggle(show: boolean); togglePosition: MatAccordionTogglePosition; _handleHeaderFocus(header: MatExpansionPanelHeader): void; _handleHeaderKeydown(event: KeyboardEvent): void; ngAfterContentInit(): void; static ngAcceptInputType_hideToggle: BooleanInput; - static ngAcceptInputType_multi: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -52,19 +52,24 @@ export declare class MatExpansionPanel extends CdkAccordionItem implements After accordion: MatAccordionBase; afterCollapse: EventEmitter; afterExpand: EventEmitter; - hideToggle: boolean; - togglePosition: MatAccordionTogglePosition; + get hideToggle(): boolean; + set hideToggle(value: boolean); + get togglePosition(): MatAccordionTogglePosition; + set togglePosition(value: MatAccordionTogglePosition); constructor(accordion: MatAccordionBase, _changeDetectorRef: ChangeDetectorRef, _uniqueSelectionDispatcher: UniqueSelectionDispatcher, _viewContainerRef: ViewContainerRef, _document: any, _animationMode: string, defaultOptions?: MatExpansionPanelDefaultOptions); _containsFocus(): boolean; _getExpandedState(): MatExpansionPanelState; _hasSpacing(): boolean; + close(): void; ngAfterContentInit(): void; ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; + open(): void; + toggle(): void; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_expanded: BooleanInput; static ngAcceptInputType_hideToggle: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -94,7 +99,7 @@ export declare class MatExpansionPanelDescription { export declare class MatExpansionPanelHeader implements OnDestroy, FocusableOption { _animationsDisabled: boolean; collapsedHeight: string; - readonly disabled: any; + get disabled(): any; expandedHeight: string; panel: MatExpansionPanel; constructor(panel: MatExpansionPanel, _element: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, defaultOptions?: MatExpansionPanelDefaultOptions); @@ -108,7 +113,7 @@ export declare class MatExpansionPanelHeader implements OnDestroy, FocusableOpti _toggle(): void; focus(origin?: FocusOrigin, options?: FocusOptions): void; ngOnDestroy(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/form-field.d.ts b/tools/public_api_guard/material/form-field.d.ts index 9652c8c341e8..00e980f05635 100644 --- a/tools/public_api_guard/material/form-field.d.ts +++ b/tools/public_api_guard/material/form-field.d.ts @@ -1,3 +1,5 @@ +export declare type FloatLabelType = 'always' | 'never' | 'auto'; + export declare function getMatFormFieldDuplicatedHintError(align: string): Error; export declare function getMatFormFieldMissingControlError(): Error; @@ -8,16 +10,17 @@ export declare const MAT_FORM_FIELD_DEFAULT_OPTIONS: InjectionToken; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatFormField extends _MatFormFieldMixinBase implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy, CanColor { _animationsEnabled: boolean; _appearance: MatFormFieldAppearance; - readonly _canLabelFloat: boolean; + get _canLabelFloat(): boolean; _connectionContainerRef: ElementRef; - _control: MatFormFieldControl; + get _control(): MatFormFieldControl; + set _control(value: MatFormFieldControl); _controlNonStatic: MatFormFieldControl; _controlStatic: MatFormFieldControl; _elementRef: ElementRef; @@ -25,19 +28,23 @@ export declare class MatFormField extends _MatFormFieldMixinBase implements Afte _hintChildren: QueryList; _hintLabelId: string; _inputContainerRef: ElementRef; - readonly _labelChild: MatLabel; + get _labelChild(): MatLabel; _labelChildNonStatic: MatLabel; _labelChildStatic: MatLabel; _labelId: string; _placeholderChild: MatPlaceholder; _prefixChildren: QueryList; - readonly _shouldAlwaysFloat: boolean; + get _shouldAlwaysFloat(): boolean; _subscriptAnimationState: string; _suffixChildren: QueryList; - appearance: MatFormFieldAppearance; - floatLabel: FloatLabelType; - hideRequiredMarker: boolean; - hintLabel: string; + get appearance(): MatFormFieldAppearance; + set appearance(value: MatFormFieldAppearance); + get floatLabel(): FloatLabelType; + set floatLabel(value: FloatLabelType); + get hideRequiredMarker(): boolean; + set hideRequiredMarker(value: boolean); + get hintLabel(): string; + set hintLabel(value: string); underlineRef: ElementRef; constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, labelOptions: LabelOptions, _dir: Directionality, _defaults: MatFormFieldDefaultOptions, _platform: Platform, _ngZone: NgZone, _animationMode: string); _animateAndLockLabel(): void; @@ -56,7 +63,7 @@ export declare class MatFormField extends _MatFormFieldMixinBase implements Afte ngOnDestroy(): void; updateOutlineGap(): void; static ngAcceptInputType_hideRequiredMarker: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -88,6 +95,7 @@ export declare abstract class MatFormFieldControl { export interface MatFormFieldDefaultOptions { appearance?: MatFormFieldAppearance; + floatLabel?: FloatLabelType; hideRequiredMarker?: boolean; } @@ -99,7 +107,7 @@ export declare class MatFormFieldModule { export declare class MatHint { align: 'start' | 'end'; id: string; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/form-field/testing.d.ts b/tools/public_api_guard/material/form-field/testing.d.ts index 3378166e4e59..959952cf82f8 100644 --- a/tools/public_api_guard/material/form-field/testing.d.ts +++ b/tools/public_api_guard/material/form-field/testing.d.ts @@ -17,7 +17,6 @@ export declare class MatFormFieldHarness extends ComponentHarness { getTextHints(): Promise; getThemeColor(): Promise<'primary' | 'accent' | 'warn'>; hasErrors(): Promise; - hasFloatingLabel(): Promise; hasLabel(): Promise; isAutofilled(): Promise; isControlDirty(): Promise; diff --git a/tools/public_api_guard/material/form-field/testing/control.d.ts b/tools/public_api_guard/material/form-field/testing/control.d.ts new file mode 100644 index 000000000000..72e4bde41094 --- /dev/null +++ b/tools/public_api_guard/material/form-field/testing/control.d.ts @@ -0,0 +1,2 @@ +export declare abstract class MatFormFieldControlHarness extends ComponentHarness { +} diff --git a/tools/public_api_guard/material/grid-list.d.ts b/tools/public_api_guard/material/grid-list.d.ts index e96ff3bddee6..e12940f1c7e1 100644 --- a/tools/public_api_guard/material/grid-list.d.ts +++ b/tools/public_api_guard/material/grid-list.d.ts @@ -5,15 +5,18 @@ export declare class MatGridAvatarCssMatStyler { export declare class MatGridList implements MatGridListBase, OnInit, AfterContentChecked { _tiles: QueryList; - cols: number; - gutterSize: string; - rowHeight: string | number; + get cols(): number; + set cols(value: number); + get gutterSize(): string; + set gutterSize(value: string); + get rowHeight(): string | number; + set rowHeight(value: string | number); constructor(_element: ElementRef, _dir: Directionality); _setListStyle(style: [string, string | null] | null): void; ngAfterContentChecked(): void; ngOnInit(): void; static ngAcceptInputType_cols: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -26,13 +29,15 @@ export declare class MatGridTile { _colspan: number; _gridList?: MatGridListBase | undefined; _rowspan: number; - colspan: number; - rowspan: number; + get colspan(): number; + set colspan(value: number); + get rowspan(): number; + set rowspan(value: number); constructor(_element: ElementRef, _gridList?: MatGridListBase | undefined); _setStyle(property: string, value: any): void; static ngAcceptInputType_colspan: NumberInput; static ngAcceptInputType_rowspan: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/grid-list/testing.d.ts b/tools/public_api_guard/material/grid-list/testing.d.ts new file mode 100644 index 000000000000..d83ffa757b10 --- /dev/null +++ b/tools/public_api_guard/material/grid-list/testing.d.ts @@ -0,0 +1,30 @@ +export interface GridListHarnessFilters extends BaseHarnessFilters { +} + +export interface GridTileHarnessFilters extends BaseHarnessFilters { + footerText?: string | RegExp; + headerText?: string | RegExp; +} + +export declare class MatGridListHarness extends ComponentHarness { + getColumns(): Promise; + getTileAtPosition({ row, column }: { + row: number; + column: number; + }): Promise; + getTiles(filters?: GridTileHarnessFilters): Promise; + static hostSelector: string; + static with(options?: GridListHarnessFilters): HarnessPredicate; +} + +export declare class MatGridTileHarness extends ComponentHarness { + getColspan(): Promise; + getFooterText(): Promise; + getHeaderText(): Promise; + getRowspan(): Promise; + hasAvatar(): Promise; + hasFooter(): Promise; + hasHeader(): Promise; + static hostSelector: string; + static with(options?: GridTileHarnessFilters): HarnessPredicate; +} diff --git a/tools/public_api_guard/material/icon.d.ts b/tools/public_api_guard/material/icon.d.ts index dbb8ac171cab..c62a80b2680d 100644 --- a/tools/public_api_guard/material/icon.d.ts +++ b/tools/public_api_guard/material/icon.d.ts @@ -23,9 +23,12 @@ export declare const MAT_ICON_LOCATION: InjectionToken; export declare function MAT_ICON_LOCATION_FACTORY(): MatIconLocation; export declare class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, AfterViewChecked, CanColor, OnDestroy { - fontIcon: string; - fontSet: string; - inline: boolean; + get fontIcon(): string; + set fontIcon(value: string); + get fontSet(): string; + set fontSet(value: string); + get inline(): boolean; + set inline(inline: boolean); svgIcon: string; constructor(elementRef: ElementRef, _iconRegistry: MatIconRegistry, ariaHidden: string, _location?: MatIconLocation | undefined, _errorHandler?: ErrorHandler | undefined); @@ -34,7 +37,7 @@ export declare class MatIcon extends _MatIconMixinBase implements OnChanges, OnI ngOnDestroy(): void; ngOnInit(): void; static ngAcceptInputType_inline: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/icon/testing.d.ts b/tools/public_api_guard/material/icon/testing.d.ts new file mode 100644 index 000000000000..00a0431b4316 --- /dev/null +++ b/tools/public_api_guard/material/icon/testing.d.ts @@ -0,0 +1,24 @@ +export declare class FakeMatIconRegistry implements PublicApi, OnDestroy { + addSvgIcon(): this; + addSvgIconInNamespace(): this; + addSvgIconLiteral(): this; + addSvgIconLiteralInNamespace(): this; + addSvgIconSet(): this; + addSvgIconSetInNamespace(): this; + addSvgIconSetLiteral(): this; + addSvgIconSetLiteralInNamespace(): this; + classNameForFontAlias(alias: string): string; + getDefaultFontSetClass(): string; + getNamedSvgIcon(): Observable; + getSvgIconFromUrl(): Observable; + ngOnDestroy(): void; + registerFontClassAlias(): this; + setDefaultFontSetClass(): this; + static ɵfac: i0.ɵɵFactoryDef; + static ɵprov: i0.ɵɵInjectableDef; +} + +export declare class MatIconTestingModule { + static ɵinj: i0.ɵɵInjectorDef; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; +} diff --git a/tools/public_api_guard/material/input.d.ts b/tools/public_api_guard/material/input.d.ts index 13f907211b98..0b026faf3dd9 100644 --- a/tools/public_api_guard/material/input.d.ts +++ b/tools/public_api_guard/material/input.d.ts @@ -19,26 +19,32 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField protected _uid: string; autofilled: boolean; controlType: string; - disabled: boolean; - readonly empty: boolean; + get disabled(): boolean; + set disabled(value: boolean); + get empty(): boolean; errorStateMatcher: ErrorStateMatcher; focused: boolean; - id: string; + get id(): string; + set id(value: string); ngControl: NgControl; placeholder: string; - readonly: boolean; - required: boolean; - readonly shouldLabelFloat: boolean; + get readonly(): boolean; + set readonly(value: boolean); + get required(): boolean; + set required(value: boolean); + get shouldLabelFloat(): boolean; readonly stateChanges: Subject; - type: string; - value: string; + get type(): string; + set type(value: string); + get value(): string; + set value(value: string); constructor(_elementRef: ElementRef, _platform: Platform, ngControl: NgControl, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone); protected _dirtyCheckNativeValue(): void; _focusChanged(isFocused: boolean): void; protected _isBadInput(): boolean; protected _isNeverEmpty(): boolean; - protected _isTextarea(): boolean; + _isTextarea(): boolean; _onInput(): void; protected _validateType(): void; focus(options?: FocusOptions): void; @@ -52,23 +58,24 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField static ngAcceptInputType_readonly: BooleanInput; static ngAcceptInputType_required: BooleanInput; static ngAcceptInputType_value: any; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatInputModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatTextareaAutosize extends CdkTextareaAutosize { - matAutosize: boolean; - matAutosizeMaxRows: number; - matAutosizeMinRows: number; - matTextareaAutosize: boolean; - static ngAcceptInputType_enabled: BooleanInput; - static ngAcceptInputType_maxRows: NumberInput; - static ngAcceptInputType_minRows: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + get matAutosize(): boolean; + set matAutosize(value: boolean); + get matAutosizeMaxRows(): number; + set matAutosizeMaxRows(value: number); + get matAutosizeMinRows(): number; + set matAutosizeMinRows(value: number); + get matTextareaAutosize(): boolean; + set matTextareaAutosize(value: boolean); + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/list.d.ts b/tools/public_api_guard/material/list.d.ts index 74d94a9c026e..519bf239fd23 100644 --- a/tools/public_api_guard/material/list.d.ts +++ b/tools/public_api_guard/material/list.d.ts @@ -1,13 +1,14 @@ export declare const MAT_SELECTION_LIST_VALUE_ACCESSOR: any; -export declare class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy { +export declare class MatList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges, OnDestroy { _stateChanges: Subject; constructor(_elementRef: ElementRef); _getListType(): 'list' | 'action-list' | null; ngOnChanges(): void; ngOnDestroy(): void; static ngAcceptInputType_disableRipple: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ngAcceptInputType_disabled: BooleanInput; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -25,13 +26,16 @@ export declare class MatListItem extends _MatListItemMixinBase implements AfterC _avatar: MatListAvatarCssMatStyler; _icon: MatListIconCssMatStyler; _lines: QueryList; + get disabled(): boolean; + set disabled(value: boolean); constructor(_element: ElementRef, _changeDetectorRef: ChangeDetectorRef, navList?: MatNavList, list?: MatList); _getHostElement(): HTMLElement; _isRippleDisabled(): boolean; ngAfterContentInit(): void; ngOnDestroy(): void; static ngAcceptInputType_disableRipple: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ngAcceptInputType_disabled: BooleanInput; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -46,11 +50,15 @@ export declare class MatListOption extends _MatListOptionMixinBase implements Af _lines: QueryList; _text: ElementRef; checkboxPosition: 'before' | 'after'; - color: ThemePalette; - disabled: any; - selected: boolean; + get color(): ThemePalette; + set color(newValue: ThemePalette); + get disabled(): any; + set disabled(value: any); + get selected(): boolean; + set selected(value: boolean); selectionList: MatSelectionList; - value: any; + get value(): any; + set value(newValue: any); constructor(_element: ElementRef, _changeDetector: ChangeDetectorRef, selectionList: MatSelectionList); _getHostElement(): HTMLElement; @@ -69,7 +77,7 @@ export declare class MatListOption extends _MatListOptionMixinBase implements Af static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_selected: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -78,12 +86,13 @@ export declare class MatListSubheaderCssMatStyler { static ɵfac: i0.ɵɵFactoryDef; } -export declare class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy { +export declare class MatNavList extends _MatListMixinBase implements CanDisable, CanDisableRipple, OnChanges, OnDestroy { _stateChanges: Subject; ngOnChanges(): void; ngOnDestroy(): void; static ngAcceptInputType_disableRipple: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ngAcceptInputType_disabled: BooleanInput; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -93,7 +102,10 @@ export declare class MatSelectionList extends _MatSelectionListMixinBase impleme _value: string[] | null; color: ThemePalette; compareWith: (o1: any, o2: any) => boolean; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); + get multiple(): boolean; + set multiple(value: boolean); options: QueryList; selectedOptions: SelectionModel; readonly selectionChange: EventEmitter; @@ -116,7 +128,8 @@ export declare class MatSelectionList extends _MatSelectionListMixinBase impleme writeValue(values: string[]): void; static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ngAcceptInputType_multiple: BooleanInput; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/menu.d.ts b/tools/public_api_guard/material/menu.d.ts index 97a23e271101..a35bf312891e 100644 --- a/tools/public_api_guard/material/menu.d.ts +++ b/tools/public_api_guard/material/menu.d.ts @@ -1,7 +1,5 @@ export declare class _MatMenu extends MatMenu { constructor(elementRef: ElementRef, ngZone: NgZone, defaultOptions: MatMenuDefaultOptions); - static ngAcceptInputType_hasBackdrop: BooleanInput; - static ngAcceptInputType_overlapTrigger: BooleanInput; static ɵcmp: i0.ɵɵComponentDefWithMeta<_MatMenu, "mat-menu", ["matMenu"], {}, {}, never>; static ɵfac: i0.ɵɵFactoryDef<_MatMenu>; } @@ -18,20 +16,25 @@ export declare class _MatMenuBase implements AfterContentInit, MatMenuPanel; readonly closed: EventEmitter; direction: Direction; - hasBackdrop: boolean | undefined; + get hasBackdrop(): boolean | undefined; + set hasBackdrop(value: boolean | undefined); items: QueryList; lazyContent: MatMenuContent; - overlapTrigger: boolean; - panelClass: string; + get overlapTrigger(): boolean; + set overlapTrigger(value: boolean); + set panelClass(classes: string); readonly panelId: string; parentMenu: MatMenuPanel | undefined; templateRef: TemplateRef; - xPosition: MenuPositionX; - yPosition: MenuPositionY; + get xPosition(): MenuPositionX; + set xPosition(value: MenuPositionX); + get yPosition(): MenuPositionY; + set yPosition(value: MenuPositionY); constructor(_elementRef: ElementRef, _ngZone: NgZone, _defaultOptions: MatMenuDefaultOptions); _handleKeydown(event: KeyboardEvent): void; _hovered(): Observable; @@ -48,7 +51,9 @@ export declare class _MatMenuBase implements AfterContentInit, MatMenuPanel; + static ngAcceptInputType_hasBackdrop: BooleanInput; + static ngAcceptInputType_overlapTrigger: BooleanInput; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatMenuBase, never, never, { "backdropClass": "backdropClass"; "ariaLabel": "aria-label"; "ariaLabelledby": "aria-labelledby"; "ariaDescribedby": "aria-describedby"; "xPosition": "xPosition"; "yPosition": "yPosition"; "overlapTrigger": "overlapTrigger"; "hasBackdrop": "hasBackdrop"; "panelClass": "class"; "classList": "classList"; }, { "closed": "closed"; "close": "close"; }, ["lazyContent", "_allItems", "items"]>; static ɵfac: i0.ɵɵFactoryDef<_MatMenuBase>; } @@ -110,7 +115,7 @@ export declare class MatMenuItem extends _MatMenuItemMixinBase implements Focusa ngOnDestroy(): void; static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -140,13 +145,15 @@ export interface MatMenuPanel { } export declare class MatMenuTrigger implements AfterContentInit, OnDestroy { - _deprecatedMatMenuTriggerFor: MatMenuPanel; + get _deprecatedMatMenuTriggerFor(): MatMenuPanel; + set _deprecatedMatMenuTriggerFor(v: MatMenuPanel); _openedBy: 'mouse' | 'touch' | null; - readonly dir: Direction; - menu: MatMenuPanel; + get dir(): Direction; + get menu(): MatMenuPanel; + set menu(menu: MatMenuPanel); readonly menuClosed: EventEmitter; menuData: any; - readonly menuOpen: boolean; + get menuOpen(): boolean; readonly menuOpened: EventEmitter; readonly onMenuClose: EventEmitter; readonly onMenuOpen: EventEmitter; @@ -162,7 +169,7 @@ export declare class MatMenuTrigger implements AfterContentInit, OnDestroy { openMenu(): void; toggleMenu(): void; triggersSubmenu(): boolean; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/paginator.d.ts b/tools/public_api_guard/material/paginator.d.ts index c7d8cba6f1d8..158777ef1fbd 100644 --- a/tools/public_api_guard/material/paginator.d.ts +++ b/tools/public_api_guard/material/paginator.d.ts @@ -1,3 +1,5 @@ +export declare const MAT_PAGINATOR_DEFAULT_OPTIONS: InjectionToken; + export declare const MAT_PAGINATOR_INTL_PROVIDER: { provide: typeof MatPaginatorIntl; deps: Optional[][]; @@ -10,14 +12,20 @@ export declare class MatPaginator extends _MatPaginatorBase implements OnInit, O _displayedPageSizeOptions: number[]; _intl: MatPaginatorIntl; color: ThemePalette; - hidePageSize: boolean; - length: number; + get hidePageSize(): boolean; + set hidePageSize(value: boolean); + get length(): number; + set length(value: number); readonly page: EventEmitter; - pageIndex: number; - pageSize: number; - pageSizeOptions: number[]; - showFirstLastButtons: boolean; - constructor(_intl: MatPaginatorIntl, _changeDetectorRef: ChangeDetectorRef); + get pageIndex(): number; + set pageIndex(value: number); + get pageSize(): number; + set pageSize(value: number); + get pageSizeOptions(): number[]; + set pageSizeOptions(value: number[]); + get showFirstLastButtons(): boolean; + set showFirstLastButtons(value: boolean); + constructor(_intl: MatPaginatorIntl, _changeDetectorRef: ChangeDetectorRef, defaults?: MatPaginatorDefaultOptions); _changePageSize(pageSize: number): void; _nextButtonsDisabled(): boolean; _previousButtonsDisabled(): boolean; @@ -36,10 +44,17 @@ export declare class MatPaginator extends _MatPaginatorBase implements OnInit, O static ngAcceptInputType_pageIndex: NumberInput; static ngAcceptInputType_pageSize: NumberInput; static ngAcceptInputType_showFirstLastButtons: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } +export interface MatPaginatorDefaultOptions { + hidePageSize?: boolean; + pageSize?: number; + pageSizeOptions?: number[]; + showFirstLastButtons?: boolean; +} + export declare class MatPaginatorIntl { readonly changes: Subject; firstPageLabel: string; diff --git a/tools/public_api_guard/material/progress-bar.d.ts b/tools/public_api_guard/material/progress-bar.d.ts index 143e1193a615..a839873d9cbf 100644 --- a/tools/public_api_guard/material/progress-bar.d.ts +++ b/tools/public_api_guard/material/progress-bar.d.ts @@ -9,10 +9,12 @@ export declare class MatProgressBar extends _MatProgressBarMixinBase implements _primaryValueBar: ElementRef; _rectangleFillValue: string; animationEnd: EventEmitter; - bufferValue: number; + get bufferValue(): number; + set bufferValue(v: number); mode: ProgressBarMode; progressbarId: string; - value: number; + get value(): number; + set value(v: number); constructor(_elementRef: ElementRef, _ngZone: NgZone, _animationMode?: string | undefined, location?: MatProgressBarLocation); _bufferTransform(): { @@ -24,7 +26,7 @@ export declare class MatProgressBar extends _MatProgressBarMixinBase implements ngAfterViewInit(): void; ngOnDestroy(): void; static ngAcceptInputType_value: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/progress-spinner.d.ts b/tools/public_api_guard/material/progress-spinner.d.ts index 011bfe018866..a0530e2ffc70 100644 --- a/tools/public_api_guard/material/progress-spinner.d.ts +++ b/tools/public_api_guard/material/progress-spinner.d.ts @@ -3,23 +3,26 @@ export declare const MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS: InjectionToken; _noopAnimations: boolean; - readonly _strokeCircumference: number; - readonly _strokeDashOffset: number | null; - readonly _viewBox: string; - diameter: number; + get _strokeCircumference(): number; + get _strokeDashOffset(): number | null; + get _viewBox(): string; + get diameter(): number; + set diameter(size: number); mode: ProgressSpinnerMode; - strokeWidth: number; - value: number; + get strokeWidth(): number; + set strokeWidth(value: number); + get value(): number; + set value(newValue: number); constructor(_elementRef: ElementRef, platform: Platform, _document: any, animationMode: string, defaults?: MatProgressSpinnerDefaultOptions); ngOnInit(): void; static ngAcceptInputType_diameter: NumberInput; static ngAcceptInputType_strokeWidth: NumberInput; static ngAcceptInputType_value: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -36,10 +39,7 @@ export declare class MatProgressSpinnerModule { export declare class MatSpinner extends MatProgressSpinner { constructor(elementRef: ElementRef, platform: Platform, document: any, animationMode: string, defaults?: MatProgressSpinnerDefaultOptions); - static ngAcceptInputType_diameter: NumberInput; - static ngAcceptInputType_strokeWidth: NumberInput; - static ngAcceptInputType_value: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/radio.d.ts b/tools/public_api_guard/material/radio.d.ts index 397d759728e2..9b047cfd8abd 100644 --- a/tools/public_api_guard/material/radio.d.ts +++ b/tools/public_api_guard/material/radio.d.ts @@ -11,16 +11,22 @@ export declare class MatRadioButton extends _MatRadioButtonMixinBase implements ariaLabel: string; ariaLabelledby: string; readonly change: EventEmitter; - checked: boolean; - color: ThemePalette; - disabled: boolean; + get checked(): boolean; + set checked(value: boolean); + get color(): ThemePalette; + set color(newValue: ThemePalette); + get disabled(): boolean; + set disabled(value: boolean); id: string; - readonly inputId: string; - labelPosition: 'before' | 'after'; + get inputId(): string; + get labelPosition(): 'before' | 'after'; + set labelPosition(value: 'before' | 'after'); name: string; radioGroup: MatRadioGroup; - required: boolean; - value: any; + get required(): boolean; + set required(value: boolean); + get value(): any; + set value(value: any); constructor(radioGroup: MatRadioGroup, elementRef: ElementRef, _changeDetector: ChangeDetectorRef, _focusMonitor: FocusMonitor, _radioDispatcher: UniqueSelectionDispatcher, _animationMode?: string | undefined, _providerOverride?: MatRadioDefaultOptions | undefined); _isRippleDisabled(): boolean; _markForCheck(): void; @@ -34,7 +40,7 @@ export declare class MatRadioButton extends _MatRadioButtonMixinBase implements static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_required: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -55,13 +61,19 @@ export declare class MatRadioGroup implements AfterContentInit, ControlValueAcce _radios: QueryList; readonly change: EventEmitter; color: ThemePalette; - disabled: boolean; - labelPosition: 'before' | 'after'; - name: string; + get disabled(): boolean; + set disabled(value: boolean); + get labelPosition(): 'before' | 'after'; + set labelPosition(v: 'before' | 'after'); + get name(): string; + set name(value: string); onTouched: () => any; - required: boolean; - selected: MatRadioButton | null; - value: any; + get required(): boolean; + set required(value: boolean); + get selected(): MatRadioButton | null; + set selected(selected: MatRadioButton | null); + get value(): any; + set value(newValue: any); constructor(_changeDetector: ChangeDetectorRef); _checkSelectedRadioButton(): void; _emitChangeEvent(): void; @@ -74,11 +86,11 @@ export declare class MatRadioGroup implements AfterContentInit, ControlValueAcce writeValue(value: any): void; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_required: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatRadioModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } diff --git a/tools/public_api_guard/material/select.d.ts b/tools/public_api_guard/material/select.d.ts index 233045f2d59c..ae5cdc6d3764 100644 --- a/tools/public_api_guard/material/select.d.ts +++ b/tools/public_api_guard/material/select.d.ts @@ -1,3 +1,5 @@ +export declare const MAT_SELECT_CONFIG: InjectionToken; + export declare const MAT_SELECT_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>; export declare const MAT_SELECT_SCROLL_STRATEGY_PROVIDER: { @@ -26,15 +28,19 @@ export declare class MatSelect extends _MatSelectMixinBase implements AfterConte _triggerRect: ClientRect; ariaLabel: string; ariaLabelledby: string; - compareWith: (o1: any, o2: any) => boolean; + get compareWith(): (o1: any, o2: any) => boolean; + set compareWith(fn: (o1: any, o2: any) => boolean); controlType: string; customTrigger: MatSelectTrigger; - disableOptionCentering: boolean; - readonly empty: boolean; + get disableOptionCentering(): boolean; + set disableOptionCentering(value: boolean); + get empty(): boolean; errorStateMatcher: ErrorStateMatcher; - readonly focused: boolean; - id: string; - multiple: boolean; + get focused(): boolean; + get id(): string; + set id(value: string); + get multiple(): boolean; + set multiple(value: boolean); ngControl: NgControl; readonly openedChange: EventEmitter; optionGroups: QueryList; @@ -45,19 +51,23 @@ export declare class MatSelect extends _MatSelectMixinBase implements AfterConte panelClass: string | string[] | Set | { [key: string]: any; }; - readonly panelOpen: boolean; - placeholder: string; - required: boolean; - readonly selected: MatOption | MatOption[]; + get panelOpen(): boolean; + get placeholder(): string; + set placeholder(value: string); + get required(): boolean; + set required(value: boolean); + get selected(): MatOption | MatOption[]; readonly selectionChange: EventEmitter; - readonly shouldLabelFloat: boolean; + get shouldLabelFloat(): boolean; sortComparator: (a: MatOption, b: MatOption, options: MatOption[]) => number; trigger: ElementRef; - readonly triggerValue: string; - typeaheadDebounceInterval: number; - value: any; + get triggerValue(): string; + get typeaheadDebounceInterval(): number; + set typeaheadDebounceInterval(value: number); + get value(): any; + set value(newValue: any); readonly valueChange: EventEmitter; - constructor(_viewportRuler: ViewportRuler, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _defaultErrorStateMatcher: ErrorStateMatcher, elementRef: ElementRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _parentFormField: MatFormField, ngControl: NgControl, tabIndex: string, scrollStrategyFactory: any, _liveAnnouncer: LiveAnnouncer); + constructor(_viewportRuler: ViewportRuler, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _defaultErrorStateMatcher: ErrorStateMatcher, elementRef: ElementRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _parentFormField: MatFormField, ngControl: NgControl, tabIndex: string, scrollStrategyFactory: any, _liveAnnouncer: LiveAnnouncer, defaults?: MatSelectConfig); _calculateOverlayScroll(selectedIndex: number, scrollBuffer: number, maxScroll: number): number; _getAriaActiveDescendant(): string | null; _getAriaLabel(): string | null; @@ -89,7 +99,7 @@ export declare class MatSelect extends _MatSelectMixinBase implements AfterConte static ngAcceptInputType_multiple: BooleanInput; static ngAcceptInputType_required: BooleanInput; static ngAcceptInputType_typeaheadDebounceInterval: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -106,6 +116,11 @@ export declare class MatSelectChange { value: any); } +export interface MatSelectConfig { + disableOptionCentering?: boolean; + typeaheadDebounceInterval?: number; +} + export declare class MatSelectModule { static ɵinj: i0.ɵɵInjectorDef; static ɵmod: i0.ɵɵNgModuleDefWithMeta; diff --git a/tools/public_api_guard/material/sidenav.d.ts b/tools/public_api_guard/material/sidenav.d.ts index bb81a8907070..3f1fe476344f 100644 --- a/tools/public_api_guard/material/sidenav.d.ts +++ b/tools/public_api_guard/material/sidenav.d.ts @@ -6,21 +6,25 @@ export declare class MatDrawer implements AfterContentInit, AfterContentChecked, _animationEnd: Subject; _animationStarted: Subject; _animationState: 'open-instant' | 'open' | 'void'; - readonly _closedStream: Observable; + get _closedStream(): Observable; _container?: MatDrawerContainer | undefined; - readonly _isFocusTrapEnabled: boolean; readonly _modeChanged: Subject; - readonly _openedStream: Observable; - readonly _width: number; - autoFocus: boolean; - readonly closedStart: Observable; - disableClose: boolean; - mode: MatDrawerMode; + get _openedStream(): Observable; + get _width(): number; + get autoFocus(): boolean; + set autoFocus(value: boolean); + get closedStart(): Observable; + get disableClose(): boolean; + set disableClose(value: boolean); + get mode(): MatDrawerMode; + set mode(value: MatDrawerMode); onPositionChanged: EventEmitter; - opened: boolean; + get opened(): boolean; + set opened(value: boolean); readonly openedChange: EventEmitter; - readonly openedStart: Observable; - position: 'start' | 'end'; + get openedStart(): Observable; + get position(): 'start' | 'end'; + set position(value: 'start' | 'end'); constructor(_elementRef: ElementRef, _focusTrapFactory: FocusTrapFactory, _focusMonitor: FocusMonitor, _platform: Platform, _ngZone: NgZone, _doc: any, _container?: MatDrawerContainer | undefined); _animationDoneListener(event: AnimationEvent): void; @@ -34,7 +38,7 @@ export declare class MatDrawer implements AfterContentInit, AfterContentChecked, static ngAcceptInputType_autoFocus: BooleanInput; static ngAcceptInputType_disableClose: BooleanInput; static ngAcceptInputType_opened: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -56,12 +60,14 @@ export declare class MatDrawerContainer implements AfterContentInit, DoCheck, On }; _drawers: QueryList; _userContent: MatDrawerContent; - autosize: boolean; + get autosize(): boolean; + set autosize(value: boolean); readonly backdropClick: EventEmitter; - readonly end: MatDrawer | null; - hasBackdrop: any; - readonly scrollable: CdkScrollable; - readonly start: MatDrawer | null; + get end(): MatDrawer | null; + get hasBackdrop(): any; + set hasBackdrop(value: any); + get scrollable(): CdkScrollable; + get start(): MatDrawer | null; constructor(_dir: Directionality, _element: ElementRef, _ngZone: NgZone, _changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, defaultAutosize?: boolean, _animationMode?: string | undefined); _closeModalDrawer(): void; _isShowingBackdrop(): boolean; @@ -74,7 +80,7 @@ export declare class MatDrawerContainer implements AfterContentInit, DoCheck, On updateContentMargins(): void; static ngAcceptInputType_autosize: BooleanInput; static ngAcceptInputType_hasBackdrop: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -91,23 +97,22 @@ export declare type MatDrawerMode = 'over' | 'push' | 'side'; export declare type MatDrawerToggleResult = 'open' | 'close'; export declare class MatSidenav extends MatDrawer { - fixedBottomGap: number; - fixedInViewport: boolean; - fixedTopGap: number; - static ngAcceptInputType_autoFocus: BooleanInput; - static ngAcceptInputType_disableClose: BooleanInput; + get fixedBottomGap(): number; + set fixedBottomGap(value: number); + get fixedInViewport(): boolean; + set fixedInViewport(value: boolean); + get fixedTopGap(): number; + set fixedTopGap(value: number); static ngAcceptInputType_fixedBottomGap: NumberInput; static ngAcceptInputType_fixedInViewport: BooleanInput; static ngAcceptInputType_fixedTopGap: NumberInput; - static ngAcceptInputType_opened: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatSidenavContainer extends MatDrawerContainer { _allDrawers: QueryList; _content: MatSidenavContent; - static ngAcceptInputType_autosize: BooleanInput; static ngAcceptInputType_hasBackdrop: BooleanInput; static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; diff --git a/tools/public_api_guard/material/slide-toggle.d.ts b/tools/public_api_guard/material/slide-toggle.d.ts index d2371a34e8b3..5eb6c8b67e2e 100644 --- a/tools/public_api_guard/material/slide-toggle.d.ts +++ b/tools/public_api_guard/material/slide-toggle.d.ts @@ -17,14 +17,16 @@ export declare class MatSlideToggle extends _MatSlideToggleMixinBase implements ariaLabel: string | null; ariaLabelledby: string | null; readonly change: EventEmitter; - checked: boolean; + get checked(): boolean; + set checked(value: boolean); defaults: MatSlideToggleDefaultOptions; readonly dragChange: EventEmitter; id: string; - readonly inputId: string; + get inputId(): string; labelPosition: 'before' | 'after'; name: string | null; - required: boolean; + get required(): boolean; + set required(value: boolean); readonly toggleChange: EventEmitter; constructor(elementRef: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, tabIndex: string, _ngZone: NgZone, defaults: MatSlideToggleDefaultOptions, _animationMode?: string | undefined, _dir?: Directionality); @@ -43,7 +45,7 @@ export declare class MatSlideToggle extends _MatSlideToggleMixinBase implements static ngAcceptInputType_disableRipple: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_required: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/slider.d.ts b/tools/public_api_guard/material/slider.d.ts index 5a6e6bb56300..d7b4c48c76e8 100644 --- a/tools/public_api_guard/material/slider.d.ts +++ b/tools/public_api_guard/material/slider.d.ts @@ -2,42 +2,52 @@ export declare const MAT_SLIDER_VALUE_ACCESSOR: any; export declare class MatSlider extends _MatSliderMixinBase implements ControlValueAccessor, OnDestroy, CanDisable, CanColor, OnInit, HasTabIndex { _animationMode?: string | undefined; - readonly _invertAxis: boolean; + protected _document?: Document; + get _invertAxis(): boolean; _isActive: boolean; - readonly _isMinValue: boolean; + get _isMinValue(): boolean; _isSliding: boolean; - readonly _thumbContainerStyles: { + get _thumbContainerStyles(): { [key: string]: string; }; - readonly _thumbGap: 7 | 10 | 0; - readonly _ticksContainerStyles: { + get _thumbGap(): 7 | 10 | 0; + get _ticksContainerStyles(): { [key: string]: string; }; - readonly _ticksStyles: { + get _ticksStyles(): { [key: string]: string; }; - readonly _trackBackgroundStyles: { + get _trackBackgroundStyles(): { [key: string]: string; }; - readonly _trackFillStyles: { + get _trackFillStyles(): { [key: string]: string; }; readonly change: EventEmitter; - readonly displayValue: string | number; + get displayValue(): string | number; displayWith: (value: number) => string | number; readonly input: EventEmitter; - invert: boolean; - max: number; - min: number; + get invert(): boolean; + set invert(value: boolean); + get max(): number; + set max(v: number); + get min(): number; + set min(v: number); onTouched: () => any; - readonly percent: number; - step: number; - thumbLabel: boolean; - tickInterval: 'auto' | number; - value: number | null; + get percent(): number; + get step(): number; + set step(v: number); + get thumbLabel(): boolean; + set thumbLabel(value: boolean); + get tickInterval(): 'auto' | number; + set tickInterval(value: 'auto' | number); + get value(): number | null; + set value(v: number | null); readonly valueChange: EventEmitter; - vertical: boolean; - constructor(elementRef: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, _dir: Directionality, tabIndex: string, _animationMode?: string | undefined, _ngZone?: NgZone | undefined); + get vertical(): boolean; + set vertical(value: boolean); + constructor(elementRef: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, _dir: Directionality, tabIndex: string, _animationMode?: string | undefined, _ngZone?: NgZone | undefined, + document?: any); _onBlur(): void; _onFocus(): void; _onKeydown(event: KeyboardEvent): void; @@ -61,7 +71,7 @@ export declare class MatSlider extends _MatSliderMixinBase implements ControlVal static ngAcceptInputType_tickInterval: NumberInput; static ngAcceptInputType_value: NumberInput; static ngAcceptInputType_vertical: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/snack-bar.d.ts b/tools/public_api_guard/material/snack-bar.d.ts index 534934649aa3..ad9209949006 100644 --- a/tools/public_api_guard/material/snack-bar.d.ts +++ b/tools/public_api_guard/material/snack-bar.d.ts @@ -5,7 +5,8 @@ export declare const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken | null; + get _openedSnackBarRef(): MatSnackBarRef | null; + set _openedSnackBarRef(value: MatSnackBarRef | null); constructor(_overlay: Overlay, _live: LiveAnnouncer, _injector: Injector, _breakpointObserver: BreakpointObserver, _parentSnackBar: MatSnackBar, _defaultConfig: MatSnackBarConfig); dismiss(): void; ngOnDestroy(): void; @@ -84,7 +85,7 @@ export declare class SimpleSnackBar { message: string; action: string; }; - readonly hasAction: boolean; + get hasAction(): boolean; snackBarRef: MatSnackBarRef; constructor(snackBarRef: MatSnackBarRef, data: any); action(): void; diff --git a/tools/public_api_guard/material/sort.d.ts b/tools/public_api_guard/material/sort.d.ts index 362c04885c7e..44b27905bb7d 100644 --- a/tools/public_api_guard/material/sort.d.ts +++ b/tools/public_api_guard/material/sort.d.ts @@ -16,8 +16,10 @@ export declare function MAT_SORT_HEADER_INTL_PROVIDER_FACTORY(parentIntl: MatSor export declare class MatSort extends _MatSortMixinBase implements CanDisable, HasInitialized, OnChanges, OnDestroy, OnInit { readonly _stateChanges: Subject; active: string; - direction: SortDirection; - disableClear: boolean; + get direction(): SortDirection; + set direction(direction: SortDirection); + get disableClear(): boolean; + set disableClear(v: boolean); readonly sortChange: EventEmitter; sortables: Map; start: 'asc' | 'desc'; @@ -30,7 +32,7 @@ export declare class MatSort extends _MatSortMixinBase implements CanDisable, Ha sort(sortable: MatSortable): void; static ngAcceptInputType_disableClear: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -58,7 +60,8 @@ export declare class MatSortHeader extends _MatSortHeaderMixinBase implements Ca _sort: MatSort; _viewState: ArrowViewStateTransition; arrowPosition: 'before' | 'after'; - disableClear: boolean; + get disableClear(): boolean; + set disableClear(v: boolean); id: string; start: 'asc' | 'desc'; constructor(_intl: MatSortHeaderIntl, changeDetectorRef: ChangeDetectorRef, _sort: MatSort, _columnDef: MatSortHeaderColumnDef, @@ -77,7 +80,7 @@ export declare class MatSortHeader extends _MatSortHeaderMixinBase implements Ca ngOnInit(): void; static ngAcceptInputType_disableClear: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/stepper.d.ts b/tools/public_api_guard/material/stepper.d.ts index ef515b442d22..ddc88544530c 100644 --- a/tools/public_api_guard/material/stepper.d.ts +++ b/tools/public_api_guard/material/stepper.d.ts @@ -11,10 +11,8 @@ export declare class MatHorizontalStepper extends MatStepper { static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -22,10 +20,6 @@ export declare class MatStep extends CdkStep implements ErrorStateMatcher { stepLabel: MatStepLabel; constructor(stepper: MatStepper, _errorStateMatcher: ErrorStateMatcher, stepperOptions?: StepperOptions); isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean; - static ngAcceptInputType_completed: BooleanInput; - static ngAcceptInputType_editable: BooleanInput; - static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_optional: BooleanInput; static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -51,7 +45,7 @@ export declare class MatStepHeader extends CdkStepHeader implements OnDestroy { _templateLabel(): MatStepLabel | null; focus(): void; ngOnDestroy(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -74,10 +68,8 @@ export declare class MatStepper extends CdkStepper implements AfterContentInit { static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -90,7 +82,7 @@ export declare class MatStepperIcon { name: StepState; templateRef: TemplateRef; constructor(templateRef: TemplateRef); - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -113,12 +105,12 @@ export declare class MatStepperModule { } export declare class MatStepperNext extends CdkStepperNext { - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatStepperPrevious extends CdkStepperPrevious { - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -127,9 +119,7 @@ export declare class MatVerticalStepper extends MatStepper { static ngAcceptInputType_completed: BooleanInput; static ngAcceptInputType_editable: BooleanInput; static ngAcceptInputType_hasError: BooleanInput; - static ngAcceptInputType_linear: BooleanInput; static ngAcceptInputType_optional: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/table.d.ts b/tools/public_api_guard/material/table.d.ts index 2719bdfe909f..149068f3b030 100644 --- a/tools/public_api_guard/material/table.d.ts +++ b/tools/public_api_guard/material/table.d.ts @@ -12,8 +12,7 @@ export declare class MatCellDef extends CdkCellDef { export declare class MatColumnDef extends CdkColumnDef { name: string; static ngAcceptInputType_sticky: BooleanInput; - static ngAcceptInputType_stickyEnd: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -35,7 +34,7 @@ export declare class MatFooterRow extends CdkFooterRow { export declare class MatFooterRowDef extends CdkFooterRowDef { static ngAcceptInputType_sticky: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -57,7 +56,7 @@ export declare class MatHeaderRow extends CdkHeaderRow { export declare class MatHeaderRowDef extends CdkHeaderRowDef { static ngAcceptInputType_sticky: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -67,25 +66,28 @@ export declare class MatRow extends CdkRow { } export declare class MatRowDef extends CdkRowDef { - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matRowDef]", never, { 'columns': "matRowDefColumns", 'when': "matRowDefWhen" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matRowDef]", never, { "columns": "matRowDefColumns"; "when": "matRowDefWhen"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class MatTable extends CdkTable { protected stickyCssClass: string; - static ngAcceptInputType_multiTemplateDataRows: BooleanInput; static ɵcmp: i0.ɵɵComponentDefWithMeta, "mat-table, table[mat-table]", ["matTable"], {}, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class MatTableDataSource extends DataSource { _renderChangesSubscription: Subscription; - data: T[]; - filter: string; + get data(): T[]; + set data(data: T[]); + get filter(): string; + set filter(filter: string); filterPredicate: ((data: T, filter: string) => boolean); filteredData: T[]; - paginator: MatPaginator | null; - sort: MatSort | null; + get paginator(): MatPaginator | null; + set paginator(paginator: MatPaginator | null); + get sort(): MatSort | null; + set sort(sort: MatSort | null); sortData: ((data: T[], sort: MatSort) => T[]); sortingDataAccessor: ((data: T, sortHeaderId: string) => string | number); constructor(initialData?: T[]); @@ -100,7 +102,7 @@ export declare class MatTableDataSource extends DataSource { export declare class MatTableModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatTextColumn extends CdkTextColumn { diff --git a/tools/public_api_guard/material/table/testing.d.ts b/tools/public_api_guard/material/table/testing.d.ts index de7736e82333..eb35b624df18 100644 --- a/tools/public_api_guard/material/table/testing.d.ts +++ b/tools/public_api_guard/material/table/testing.d.ts @@ -15,6 +15,7 @@ export declare class MatFooterCellHarness extends MatCellHarness { } export declare class MatFooterRowHarness extends ComponentHarness { + getCellTextByColumnName(): Promise; getCellTextByIndex(filter?: CellHarnessFilters): Promise; getCells(filter?: CellHarnessFilters): Promise; static hostSelector: string; @@ -27,6 +28,7 @@ export declare class MatHeaderCellHarness extends MatCellHarness { } export declare class MatHeaderRowHarness extends ComponentHarness { + getCellTextByColumnName(): Promise; getCellTextByIndex(filter?: CellHarnessFilters): Promise; getCells(filter?: CellHarnessFilters): Promise; static hostSelector: string; @@ -34,12 +36,17 @@ export declare class MatHeaderRowHarness extends ComponentHarness { } export declare class MatRowHarness extends ComponentHarness { + getCellTextByColumnName(): Promise; getCellTextByIndex(filter?: CellHarnessFilters): Promise; getCells(filter?: CellHarnessFilters): Promise; static hostSelector: string; static with(options?: RowHarnessFilters): HarnessPredicate; } +export interface MatRowHarnessColumnsText { + [columnName: string]: string; +} + export declare class MatTableHarness extends ComponentHarness { getCellTextByColumnName(): Promise; getCellTextByIndex(): Promise; diff --git a/tools/public_api_guard/material/tabs.d.ts b/tools/public_api_guard/material/tabs.d.ts index be4f181317be..663fd5ea4712 100644 --- a/tools/public_api_guard/material/tabs.d.ts +++ b/tools/public_api_guard/material/tabs.d.ts @@ -18,14 +18,14 @@ export declare abstract class _MatTabBodyBase implements OnInit, OnDestroy { _translateTabComplete: Subject; animationDuration: string; origin: number | null; - position: number; + set position(position: number); constructor(_elementRef: ElementRef, _dir: Directionality, changeDetectorRef: ChangeDetectorRef); _getLayoutDirection(): Direction; _isCenterPosition(position: MatTabBodyPositionState | string): boolean; _onTranslateTabStarted(event: AnimationEvent): void; ngOnDestroy(): void; ngOnInit(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabBodyBase, never, never, { '_content': "content", 'origin': "origin", 'animationDuration': "animationDuration", 'position': "position" }, { '_onCentering': "_onCentering", '_beforeCentering': "_beforeCentering", '_afterLeavingCenter': "_afterLeavingCenter", '_onCentered': "_onCentered" }, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabBodyBase, never, never, { "_content": "content"; "origin": "origin"; "animationDuration": "animationDuration"; "position": "position"; }, { "_onCentering": "_onCentering"; "_beforeCentering": "_beforeCentering"; "_afterLeavingCenter": "_afterLeavingCenter"; "_onCentered": "_onCentered"; }, never>; static ɵfac: i0.ɵɵFactoryDef<_MatTabBodyBase>; } @@ -37,13 +37,17 @@ export declare abstract class _MatTabGroupBase extends _MatTabGroupMixinBase imp abstract _tabHeader: MatTabGroupBaseHeader; _tabs: QueryList; readonly animationDone: EventEmitter; - animationDuration: string; - backgroundColor: ThemePalette; + get animationDuration(): string; + set animationDuration(value: string); + get backgroundColor(): ThemePalette; + set backgroundColor(value: ThemePalette); disablePagination: boolean; - dynamicHeight: boolean; + get dynamicHeight(): boolean; + set dynamicHeight(value: boolean); readonly focusChange: EventEmitter; headerPosition: MatTabHeaderPosition; - selectedIndex: number | null; + get selectedIndex(): number | null; + set selectedIndex(value: number | null); readonly selectedIndexChange: EventEmitter; readonly selectedTabChange: EventEmitter; constructor(elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, defaultConfig?: MatTabsConfig, _animationMode?: string | undefined); @@ -58,28 +62,36 @@ export declare abstract class _MatTabGroupBase extends _MatTabGroupMixinBase imp ngAfterContentInit(): void; ngOnDestroy(): void; realignInkBar(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabGroupBase, never, never, { 'dynamicHeight': "dynamicHeight", 'selectedIndex': "selectedIndex", 'headerPosition': "headerPosition", 'animationDuration': "animationDuration", 'disablePagination': "disablePagination", 'backgroundColor': "backgroundColor" }, { 'selectedIndexChange': "selectedIndexChange", 'focusChange': "focusChange", 'animationDone': "animationDone", 'selectedTabChange': "selectedTabChange" }, never>; + static ngAcceptInputType_animationDuration: NumberInput; + static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_dynamicHeight: BooleanInput; + static ngAcceptInputType_selectedIndex: NumberInput; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabGroupBase, never, never, { "dynamicHeight": "dynamicHeight"; "selectedIndex": "selectedIndex"; "headerPosition": "headerPosition"; "animationDuration": "animationDuration"; "disablePagination": "disablePagination"; "backgroundColor": "backgroundColor"; }, { "selectedIndexChange": "selectedIndexChange"; "focusChange": "focusChange"; "animationDone": "animationDone"; "selectedTabChange": "selectedTabChange"; }, never>; static ɵfac: i0.ɵɵFactoryDef<_MatTabGroupBase>; } export declare abstract class _MatTabHeaderBase extends MatPaginatedTabHeader implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy { - disableRipple: any; + get disableRipple(): any; + set disableRipple(value: any); constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, dir: Directionality, ngZone: NgZone, platform: Platform, animationMode?: string); protected _itemSelected(event: KeyboardEvent): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabHeaderBase, never, never, { 'disableRipple': "disableRipple" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabHeaderBase, never, never, { "disableRipple": "disableRipple"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef<_MatTabHeaderBase>; } export declare class _MatTabLinkBase extends _MatTabLinkMixinBase implements OnDestroy, CanDisable, CanDisableRipple, HasTabIndex, RippleTarget, FocusableOption { protected _isActive: boolean; - active: boolean; + get active(): boolean; + set active(value: boolean); elementRef: ElementRef; rippleConfig: RippleConfig & RippleGlobalOptions; - readonly rippleDisabled: boolean; + get rippleDisabled(): boolean; constructor(_tabNavBar: _MatTabNavBase, elementRef: ElementRef, globalRippleOptions: RippleGlobalOptions | null, tabIndex: string, _focusMonitor: FocusMonitor, animationMode?: string); focus(): void; ngOnDestroy(): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabLinkBase, never, never, { 'active': "active" }, {}, never>; + static ngAcceptInputType_disableRipple: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabLinkBase, never, never, { "active": "active"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef<_MatTabLinkBase>; } @@ -87,15 +99,17 @@ export declare abstract class _MatTabNavBase extends MatPaginatedTabHeader imple abstract _items: QueryList; - backgroundColor: ThemePalette; + get backgroundColor(): ThemePalette; + set backgroundColor(value: ThemePalette); color: ThemePalette; - disableRipple: any; + get disableRipple(): any; + set disableRipple(value: any); constructor(elementRef: ElementRef, dir: Directionality, ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, platform?: Platform, animationMode?: string); protected _itemSelected(): void; ngAfterContentInit(): void; updateActiveLink(_element?: ElementRef): void; - static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabNavBase, never, never, { 'backgroundColor': "backgroundColor", 'disableRipple': "disableRipple", 'color': "color" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatTabNavBase, never, never, { "backgroundColor": "backgroundColor"; "disableRipple": "disableRipple"; "color": "color"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef<_MatTabNavBase>; } @@ -120,11 +134,12 @@ export declare class MatTab extends _MatTabMixinBase implements OnInit, CanDisab readonly _stateChanges: Subject; ariaLabel: string; ariaLabelledby: string; - readonly content: TemplatePortal | null; + get content(): TemplatePortal | null; isActive: boolean; origin: number | null; position: number | null; - templateLabel: MatTabLabel; + get templateLabel(): MatTabLabel; + set templateLabel(value: MatTabLabel); textLabel: string; constructor(_viewContainerRef: ViewContainerRef, _closestTabGroup?: any); @@ -132,7 +147,7 @@ export declare class MatTab extends _MatTabMixinBase implements OnInit, CanDisab ngOnDestroy(): void; ngOnInit(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -173,11 +188,7 @@ export declare class MatTabGroup extends _MatTabGroupBase { _tabBodyWrapper: ElementRef; _tabHeader: MatTabGroupBaseHeader; constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, defaultConfig?: MatTabsConfig, animationMode?: string); - static ngAcceptInputType_animationDuration: NumberInput; - static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_dynamicHeight: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -190,8 +201,7 @@ export declare class MatTabHeader extends _MatTabHeaderBase { _tabListContainer: ElementRef; constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, dir: Directionality, ngZone: NgZone, platform: Platform, animationMode?: string); static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -209,16 +219,14 @@ export declare class MatTabLabelWrapper extends _MatTabLabelWrapperMixinBase imp getOffsetLeft(): number; getOffsetWidth(): number; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } export declare class MatTabLink extends _MatTabLinkBase implements OnDestroy { constructor(tabNavBar: MatTabNav, elementRef: ElementRef, ngZone: NgZone, platform: Platform, globalRippleOptions: RippleGlobalOptions | null, tabIndex: string, focusMonitor: FocusMonitor, animationMode?: string); ngOnDestroy(): void; - static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } @@ -232,8 +240,7 @@ export declare class MatTabNav extends _MatTabNavBase { constructor(elementRef: ElementRef, dir: Directionality, ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, platform?: Platform, animationMode?: string); static ngAcceptInputType_disableRipple: BooleanInput; - static ngAcceptInputType_selectedIndex: NumberInput; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/toolbar.d.ts b/tools/public_api_guard/material/toolbar.d.ts index dd8e94f268c8..c42af6b32c70 100644 --- a/tools/public_api_guard/material/toolbar.d.ts +++ b/tools/public_api_guard/material/toolbar.d.ts @@ -2,7 +2,7 @@ export declare class MatToolbar extends _MatToolbarMixinBase implements CanColor _toolbarRows: QueryList; constructor(elementRef: ElementRef, _platform: Platform, document?: any); ngAfterViewInit(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/tooltip.d.ts b/tools/public_api_guard/material/tooltip.d.ts index 22014a729205..2e895465e98b 100644 --- a/tools/public_api_guard/material/tooltip.d.ts +++ b/tools/public_api_guard/material/tooltip.d.ts @@ -17,14 +17,20 @@ export declare const MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER: { export declare class MatTooltip implements OnDestroy, OnInit { _overlayRef: OverlayRef | null; _tooltipInstance: TooltipComponent | null; - disabled: boolean; + get disabled(): boolean; + set disabled(value: boolean); hideDelay: number; - message: string; - position: TooltipPosition; + get message(): string; + set message(value: string); + get position(): TooltipPosition; + set position(value: TooltipPosition); showDelay: number; - tooltipClass: string | string[] | Set | { + get tooltipClass(): string | string[] | Set | { [key: string]: any; }; + set tooltipClass(value: string | string[] | Set | { + [key: string]: any; + }); touchGestures: TooltipTouchGestures; constructor(_overlay: Overlay, _elementRef: ElementRef, _scrollDispatcher: ScrollDispatcher, _viewContainerRef: ViewContainerRef, _ngZone: NgZone, _platform: Platform, _ariaDescriber: AriaDescriber, _focusMonitor: FocusMonitor, scrollStrategy: any, _dir: Directionality, _defaultOptions: MatTooltipDefaultOptions, _hammerLoader?: any); @@ -45,7 +51,7 @@ export declare class MatTooltip implements OnDestroy, OnInit { static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_hideDelay: NumberInput; static ngAcceptInputType_showDelay: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/public_api_guard/material/tree.d.ts b/tools/public_api_guard/material/tree.d.ts index e3959752094f..33710f628ef0 100644 --- a/tools/public_api_guard/material/tree.d.ts +++ b/tools/public_api_guard/material/tree.d.ts @@ -2,14 +2,16 @@ export declare class MatNestedTreeNode extends CdkNestedTreeNode implement protected _differs: IterableDiffers; protected _elementRef: ElementRef; protected _tree: CdkTree; - disabled: any; + get disabled(): any; + set disabled(value: any); node: T; - tabIndex: number; + get tabIndex(): number; + set tabIndex(value: number); constructor(_elementRef: ElementRef, _tree: CdkTree, _differs: IterableDiffers, tabIndex: string); ngAfterContentInit(): void; ngOnDestroy(): void; static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "mat-nested-tree-node", ["matNestedTreeNode"], { 'node': "matNestedTreeNode", 'disabled': "disabled", 'tabIndex': "tabIndex" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "mat-nested-tree-node", ["matNestedTreeNode"], { "node": "matNestedTreeNode"; "disabled": "disabled"; "tabIndex": "tabIndex"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -23,7 +25,8 @@ export declare class MatTreeFlatDataSource extends DataSource { _data: BehaviorSubject; _expandedData: BehaviorSubject; _flattenedData: BehaviorSubject; - data: T[]; + get data(): T[]; + set data(value: T[]); constructor(_treeControl: FlatTreeControl, _treeFlattener: MatTreeFlattener, initialData?: T[]); connect(collectionViewer: CollectionViewer): Observable; disconnect(): void; @@ -43,12 +46,13 @@ export declare class MatTreeFlattener { export declare class MatTreeModule { static ɵinj: i0.ɵɵInjectorDef; - static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵmod: i0.ɵɵNgModuleDefWithMeta; } export declare class MatTreeNestedDataSource extends DataSource { _data: BehaviorSubject; - data: T[]; + get data(): T[]; + set data(value: T[]); connect(collectionViewer: CollectionViewer): Observable; disconnect(): void; } @@ -59,13 +63,13 @@ export declare class MatTreeNode extends _MatTreeNodeMixinBase implements role: 'treeitem' | 'group'; constructor(_elementRef: ElementRef, _tree: CdkTree, tabIndex: string); static ngAcceptInputType_disabled: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "mat-tree-node", ["matTreeNode"], { 'disabled': "disabled", 'tabIndex': "tabIndex", 'role': "role" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "mat-tree-node", ["matTreeNode"], { "disabled": "disabled"; "tabIndex": "tabIndex"; "role": "role"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class MatTreeNodeDef extends CdkTreeNodeDef { data: T; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodeDef]", never, { 'when': "matTreeNodeDefWhen", 'data': "matTreeNode" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodeDef]", never, { "when": "matTreeNodeDefWhen"; "data": "matTreeNode"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } @@ -80,14 +84,12 @@ export declare class MatTreeNodeOutlet implements CdkTreeNodeOutlet { export declare class MatTreeNodePadding extends CdkTreeNodePadding { indent: number; level: number; - static ngAcceptInputType_level: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodePadding]", never, { 'level': "matTreeNodePadding", 'indent': "matTreeNodePaddingIndent" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodePadding]", never, { "level": "matTreeNodePadding"; "indent": "matTreeNodePaddingIndent"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } export declare class MatTreeNodeToggle extends CdkTreeNodeToggle { recursive: boolean; - static ngAcceptInputType_recursive: BooleanInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodeToggle]", never, { 'recursive': "matTreeNodeToggleRecursive" }, {}, never>; + static ɵdir: i0.ɵɵDirectiveDefWithMeta, "[matTreeNodeToggle]", never, { "recursive": "matTreeNodeToggleRecursive"; }, {}, never>; static ɵfac: i0.ɵɵFactoryDef>; } diff --git a/tools/public_api_guard/youtube-player/youtube-player.d.ts b/tools/public_api_guard/youtube-player/youtube-player.d.ts index 6a6c487e639f..d882ddd19145 100644 --- a/tools/public_api_guard/youtube-player/youtube-player.d.ts +++ b/tools/public_api_guard/youtube-player/youtube-player.d.ts @@ -1,17 +1,20 @@ export declare class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { apiChange: EventEmitter; - endSeconds: number | undefined; + set endSeconds(endSeconds: number | undefined); error: EventEmitter; - height: number | undefined; + get height(): number | undefined; + set height(height: number | undefined); playbackQualityChange: EventEmitter; playbackRateChange: EventEmitter; ready: EventEmitter; showBeforeIframeApiLoads: boolean | undefined; - startSeconds: number | undefined; + set startSeconds(startSeconds: number | undefined); stateChange: EventEmitter; - suggestedQuality: YT.SuggestedVideoQuality | undefined; - videoId: string | undefined; - width: number | undefined; + set suggestedQuality(suggestedQuality: YT.SuggestedVideoQuality | undefined); + get videoId(): string | undefined; + set videoId(videoId: string | undefined); + get width(): number | undefined; + set width(width: number | undefined); youtubeContainer: ElementRef; constructor(_ngZone: NgZone, platformId?: Object); @@ -39,7 +42,7 @@ export declare class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { setVolume(volume: number): void; stopVideo(): void; unMute(): void; - static ɵcmp: i0.ɵɵComponentDefWithMeta; + static ɵcmp: i0.ɵɵComponentDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/tools/release/base-release-task.ts b/tools/release/base-release-task.ts index 2ddf148b89c9..9153a0164c66 100644 --- a/tools/release/base-release-task.ts +++ b/tools/release/base-release-task.ts @@ -13,7 +13,7 @@ export class BaseReleaseTask { constructor(public git: GitClient) {} /** Checks if the user is on an allowed publish branch for the specified version. */ - protected switchToPublishBranch(newVersion: Version): string { + protected async assertValidPublishBranch(newVersion: Version): Promise { const allowedBranches = getAllowedPublishBranches(newVersion); const currentBranchName = this.git.getCurrentBranch(); @@ -24,30 +24,24 @@ export class BaseReleaseTask { return currentBranchName; } - // In case there are multiple allowed publish branches for this version, we just - // exit and let the user decide which branch they want to release from. - if (allowedBranches.length !== 1) { - console.warn(chalk.yellow(' ✘ You are not on an allowed publish branch.')); - console.warn(chalk.yellow(` Please switch to one of the following branches: ` + - `${allowedBranches.join(', ')}`)); - process.exit(0); - } - - // For this version there is only *one* allowed publish branch, so we could - // automatically switch to that branch in case the user isn't on it yet. - const defaultPublishBranch = allowedBranches[0]; + console.error(chalk.red(' ✘ You are not on an allowed publish branch.')); + console.info(chalk.yellow( + ` Allowed branches are: ${chalk.bold(allowedBranches.join(', '))}`)); + console.info(); - if (!this.git.checkoutBranch(defaultPublishBranch)) { - console.error(chalk.red( - ` ✘ Could not switch to the "${chalk.italic(defaultPublishBranch)}" branch.`)); - console.error(chalk.red( - ` Please ensure that the branch exists or manually switch to the branch.`)); - process.exit(1); + // Prompt the user if they wants to forcibly use the current branch. We support this + // because in some cases, releases do not use the common publish branches. e.g. a major + // release is delayed, and new features for the next minor version are collected. + if (await this.promptConfirm( + `Do you want to forcibly use the current branch? (${chalk.italic(currentBranchName)})`)) { + console.log(); + console.log(chalk.green(` ✓ Using the "${chalk.italic(currentBranchName)}" branch.`)); + return currentBranchName; } - console.log(chalk.green( - ` ✓ Switched to the "${chalk.italic(defaultPublishBranch)}" branch.`)); - return defaultPublishBranch; + console.warn(); + console.warn(chalk.yellow(' Please switch to one of the allowed publish branches.')); + process.exit(0); } /** Verifies that the local branch is up to date with the given publish branch. */ @@ -73,11 +67,12 @@ export class BaseReleaseTask { } /** Prompts the user with a confirmation question and a specified message. */ - protected async promptConfirm(message: string): Promise { + protected async promptConfirm(message: string, defaultValue = false): Promise { return (await prompt<{result: boolean}>({ type: 'confirm', name: 'result', message: message, + default: defaultValue, })).result; } } diff --git a/tools/release/publish-release.ts b/tools/release/publish-release.ts index 15c546ebcb1a..ac093d5d7a75 100644 --- a/tools/release/publish-release.ts +++ b/tools/release/publish-release.ts @@ -76,7 +76,7 @@ class PublishReleaseTask extends BaseReleaseTask { this.verifyNoUncommittedChanges(); // Branch that will be used to build the output for the release of the current version. - const publishBranch = this.switchToPublishBranch(newVersion); + const publishBranch = await this.assertValidPublishBranch(newVersion); this._verifyLastCommitFromStagingScript(); this.verifyLocalCommitsMatchUpstream(publishBranch); diff --git a/tools/release/stage-release.ts b/tools/release/stage-release.ts index 697a0fd03152..3ada109987a8 100644 --- a/tools/release/stage-release.ts +++ b/tools/release/stage-release.ts @@ -92,7 +92,7 @@ class StageReleaseTask extends BaseReleaseTask { this.verifyNoUncommittedChanges(); // Branch that will be used to stage the release for the new selected version. - const publishBranch = this.switchToPublishBranch(newVersion); + const publishBranch = await this.assertValidPublishBranch(newVersion); this.verifyLocalCommitsMatchUpstream(publishBranch); this._verifyAngularPeerDependencyVersion(newVersion); diff --git a/tools/system-config-tmpl.js b/tools/system-config-tmpl.js new file mode 100644 index 000000000000..4a5eb7d950f1 --- /dev/null +++ b/tools/system-config-tmpl.js @@ -0,0 +1,181 @@ +/** + * @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 + */ + +// Note that this file isn't being transpiled so we need to keep it in ES5. Also +// identifiers of the format "$NAME_TMPL" will be replaced by the Bazel rule that +// converts this template file into the actual SystemJS configuration file. + +var CDK_PACKAGES = $CDK_ENTRYPOINTS_TMPL; +var CDK_EXPERIMENTAL_PACKAGES = $CDK_EXPERIMENTAL_ENTRYPOINTS_TMPL; +var MATERIAL_PACKAGES = $MATERIAL_ENTRYPOINTS_TMPL; +var MATERIAL_EXPERIMENTAL_PACKAGES = $MATERIAL_EXPERIMENTAL_ENTRYPOINTS_TMPL; + +/** Map of Angular framework packages and their bundle names. */ +var frameworkPackages = $ANGULAR_PACKAGE_BUNDLES; + +/** Whether Ivy is enabled. */ +var isRunningWithIvy = '$ANGULAR_IVY_ENABLED_TMPL'.toString() === 'True'; + +/** Path that relatively resolves to the directory that contains all packages. */ +var packagesPath = '$PACKAGES_DIR'; + +/** Path that relatively resolves to the node_modules directory. */ +var nodeModulesPath = '$NODE_MODULES_BASE_PATH'; + +/** Path mappings that will be registered in SystemJS. */ +var pathMapping = { + 'tslib': 'node:tslib/tslib.js', + 'moment': 'node:moment/min/moment-with-locales.min.js', + + 'rxjs': 'node:rxjs/bundles/rxjs.umd.min.js', + 'rxjs/operators': 'tools/system-rxjs-operators.js', + + // MDC Web + '@material/animation': 'node:@material/animation/dist/mdc.animation.js', + '@material/auto-init': 'node:@material/auto-init/dist/mdc.autoInit.js', + '@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/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', + '@material/floating-label': 'node:@material/floating-label/dist/mdc.floatingLabel.js', + '@material/form-field': 'node:@material/form-field/dist/mdc.formField.js', + '@material/icon-button': 'node:@material/icon-button/dist/mdc.iconButton.js', + '@material/line-ripple': 'node:@material/line-ripple/dist/mdc.lineRipple.js', + '@material/linear-progress': 'node:@material/linear-progress/dist/mdc.linearProgress.js', + '@material/list': 'node:@material/list/dist/mdc.list.js', + '@material/menu': 'node:@material/menu/dist/mdc.menu.js', + '@material/menu-surface': 'node:@material/menu-surface/dist/mdc.menuSurface.js', + '@material/notched-outline': 'node:@material/notched-outline/dist/mdc.notchedOutline.js', + '@material/radio': 'node:@material/radio/dist/mdc.radio.js', + '@material/ripple': 'node:@material/ripple/dist/mdc.ripple.js', + '@material/select': 'node:@material/select/dist/mdc.select.js', + '@material/slider': 'node:@material/slider/dist/mdc.slider.js', + '@material/snackbar': 'node:@material/snackbar/dist/mdc.snackbar.js', + '@material/switch': 'node:@material/switch/dist/mdc.switch.js', + '@material/tab': 'node:@material/tab/dist/mdc.tab.js', + '@material/tab-bar': 'node:@material/tab-bar/dist/mdc.tabBar.js', + '@material/tab-indicator': 'node:@material/tab-indicator/dist/mdc.tabIndicator.js', + '@material/tab-scroller': 'node:@material/tab-scroller/dist/mdc.tabScroller.js', + '@material/textfield': 'node:@material/textfield/dist/mdc.textfield.js', + '@material/top-app-bar': 'node:@material/top-app-bar/dist/mdc.topAppBar.js' +}; + +/** Package configurations that will be used in SystemJS. */ +var packagesConfig = { + // Set the default extension for the root package. Needed for imports to source files + // without explicit extension. This is common in CommonJS. + '.': {defaultExtension: 'js'}, +}; + +// Manual directories that need to be configured too. These directories are not +// public entry-points, but they are imported in source files as if they were. In order +// to ensure that the directory imports properly resolve to the "index.js" files within +// SystemJS, we configure them similar to actual package entry-points. +CDK_PACKAGES.push('testing/private', 'testing/testbed/fake-events'); +MATERIAL_PACKAGES.push('testing'); + +// Configure framework packages. +setupFrameworkPackages(); + +// Configure Angular components packages/entry-points. +setupLocalReleasePackages(); + +// Configure the base path and map the different node packages. +System.config({ + baseURL: '$BASE_URL', + map: pathMapping, + packages: packagesConfig, + paths: { + 'node:*': nodeModulesPath + '*', + } +}); + +/** + * Walks through all interpolated Angular Framework packages and configures + * them in SystemJS. Framework packages should always resolve to the UMD bundles. + */ +function setupFrameworkPackages() { + Object.keys(frameworkPackages).forEach(function(moduleName) { + var primaryEntryPointSegments = moduleName.split('-'); + // Ensures that imports to the framework package are resolved + // to the configured node modules directory. + pathMapping[moduleName] = 'node:' + moduleName; + // Configure each bundle for the current package. + frameworkPackages[moduleName].forEach(function(bundleName) { + // Entry-point segments determined from the UMD bundle name. We split the + // bundle into segments based on dashes. We omit the leading segments that + // belong to the primary entry-point module name since we are only interested + // in the segments that build up the secondary or tertiary entry-point name. + var segments = bundleName.substring(0, bundleName.length - '.umd.js'.length) + .split('-') + .slice(primaryEntryPointSegments.length); + // The entry-point name. For secondary entry-points we determine the name from + // the UMD bundle names. e.g. "animations-browser" results in "@angular/animations/browser". + var entryPointName = segments.length ? moduleName + '/' + segments.join('/') : moduleName; + var bundlePath = 'bundles/' + bundleName; + // When running with Ivy, we need to load the ngcc processed UMD bundles. + // These are stored in the `__ivy_ngcc_` folder that has been generated + // since we run ngcc with `--create-ivy-entry-points`. Filter out the compiler + // package because it won't be processed by ngcc. + if (isRunningWithIvy && entryPointName !== '@angular/compiler') { + bundlePath = '__ivy_ngcc__/' + bundlePath; + } + packagesConfig[entryPointName] = { + main: segments + .map(function() { + return '../' + }) + .join('') + + bundlePath + }; + }); + }); +} + +/** Configures the local release packages in SystemJS */ +function setupLocalReleasePackages() { + // Configure all primary entry-points. + configureEntryPoint('cdk'); + configureEntryPoint('cdk-experimental'); + configureEntryPoint('components-examples'); + configureEntryPoint('material'); + configureEntryPoint('material-experimental'); + configureEntryPoint('material-moment-adapter'); + + // Configure all secondary entry-points. + CDK_PACKAGES.forEach(function(pkgName) { + configureEntryPoint('cdk', pkgName); + }); + CDK_EXPERIMENTAL_PACKAGES.forEach(function(pkgName) { + configureEntryPoint('cdk-experimental', pkgName); + }); + MATERIAL_EXPERIMENTAL_PACKAGES.forEach(function(pkgName) { + configureEntryPoint('material-experimental', pkgName); + }); + MATERIAL_PACKAGES.forEach(function(pkgName) { + configureEntryPoint('material', pkgName); + }); + configureEntryPoint('google-maps'); + configureEntryPoint('youtube-player'); +} + +/** Configures the specified package, its entry-point and its examples. */ +function configureEntryPoint(pkgName, entryPoint) { + var name = entryPoint ? pkgName + '/' + entryPoint : pkgName; + var examplesName = 'components-examples/' + name; + + pathMapping['@angular/' + name] = packagesPath + '/' + name; + pathMapping['@angular/' + examplesName] = packagesPath + '/' + examplesName; + + // Ensure that imports which resolve to the entry-point directory are + // redirected to the "index.js" file of the directory. + packagesConfig[packagesPath + '/' + name] = + packagesConfig[packagesPath + '/' + examplesName] = {main: 'index.js'}; +} diff --git a/src/dev-app/system-rxjs-operators.js b/tools/system-rxjs-operators.js similarity index 100% rename from src/dev-app/system-rxjs-operators.js rename to tools/system-rxjs-operators.js diff --git a/tools/tslint-rules/coercionTypesRule.ts b/tools/tslint-rules/coercionTypesRule.ts index e2dbae1811cc..dc08f0d5d917 100644 --- a/tools/tslint-rules/coercionTypesRule.ts +++ b/tools/tslint-rules/coercionTypesRule.ts @@ -4,6 +4,17 @@ import * as tsutils from 'tsutils'; const TYPE_ACCEPT_MEMBER_PREFIX = 'ngAcceptInputType_'; +/** + * Type that describes the TypeScript type checker with internal methods for + * the type relation API methods being exposed. + */ +// TODO: remove if https://github.com/microsoft/TypeScript/issues/9879 is resolved. +type TypeCheckerWithRelationApi = ts.TypeChecker & { + getNullType: () => ts.Type; + getUndefinedType: () => ts.Type; + isTypeAssignableTo: (a: ts.Type, b: ts.Type) => boolean; +}; + /** * TSLint rule that verifies that classes declare corresponding `ngAcceptInputType_*` * static fields for inputs that use coercion inside of their setters. Also handles @@ -11,7 +22,8 @@ const TYPE_ACCEPT_MEMBER_PREFIX = 'ngAcceptInputType_'; */ export class Rule extends Lint.Rules.TypedRule { applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - const walker = new Walker(sourceFile, this.getOptions(), program.getTypeChecker()); + const walker = new Walker(sourceFile, this.getOptions(), + program.getTypeChecker() as TypeCheckerWithRelationApi); return this.applyWithWalker(walker); } } @@ -23,9 +35,15 @@ class Walker extends Lint.RuleWalker { /** Mapping of interfaces known to have coercion properties and the property names themselves. */ private _coercionInterfaces: {[interfaceName: string]: string[]}; + /** Type resolving to the TS internal `null` type. */ + private _nullType = this._typeChecker.getNullType(); + + /** Type resolving to the TS internal `undefined` type. */ + private _undefinedType = this._typeChecker.getUndefinedType(); + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, - private _typeChecker: ts.TypeChecker) { + private _typeChecker: TypeCheckerWithRelationApi) { super(sourceFile, options); this._coercionFunctions = new Set(options.ruleArguments[0] || []); this._coercionInterfaces = options.ruleArguments[1] || {}; @@ -40,9 +58,9 @@ class Walker extends Lint.RuleWalker { visitClassDeclaration(node: ts.ClassDeclaration) { if (this._shouldLintClass(node)) { - this._lintClass(node, node); + this._lintClass(node, node, true); this._lintSuperClasses(node); - this._lintInterfaces(node, node); + this._lintInterfaces(node, node, true); } super.visitClassDeclaration(node); } @@ -58,34 +76,9 @@ class Walker extends Lint.RuleWalker { return; } - if (node.type.kind === ts.SyntaxKind.AnyKeyword) { - // if the type is "any", then it can be "null" and "undefined" too. - return; - } else if ( - ts.isTypeReferenceNode(node.type) && ts.isIdentifier(node.type.typeName) && - (node.type.typeName.text === 'BooleanInput' || node.type.typeName.text === 'NumberInput')) { - // if the type is "BooleanInput" or "NumberInput", we don't need to check more. Ideally, - // we'd not have any of these hardcoded checks at all, and just rely on type assignability - // checks, but this is only programmatically possible with TypeScript 3.7. See: - // https://github.com/microsoft/TypeScript/issues/9879 - return; - } else if (!ts.isUnionTypeNode(node.type)) { - this.addFailureAtNode( - node, - 'Acceptance member does not have an union type. The member ' + - 'should use an union type to also accept "null" and "undefined".'); - return; - } - - let hasNull = false; - let hasUndefined = false; - for (let type of node.type.types) { - if (type.kind === ts.SyntaxKind.NullKeyword) { - hasNull = true; - } else if (type.kind === ts.SyntaxKind.UndefinedKeyword) { - hasUndefined = true; - } - } + const type = this._typeChecker.getTypeFromTypeNode(node.type); + const hasUndefined = this._typeChecker.isTypeAssignableTo(this._undefinedType, type); + const hasNull = this._typeChecker.isTypeAssignableTo(this._nullType, type); if (!hasNull && !hasUndefined) { this.addFailureAtNode( @@ -107,12 +100,14 @@ class Walker extends Lint.RuleWalker { * @param node Class declaration to be checked. * @param sourceClass Class declaration on which to look for static properties that declare the * accepted values for the setter. + * @param expectDeclaredMembers Whether acceptance members should be expected or unexpected. */ - private _lintClass(node: ts.ClassDeclaration, sourceClass: ts.ClassDeclaration): void { + private _lintClass(node: ts.ClassDeclaration, sourceClass: ts.ClassDeclaration, + expectDeclaredMembers: boolean): void { node.members.forEach(member => { if (ts.isSetAccessor(member) && usesCoercion(member, this._coercionFunctions) && this._shouldCheckSetter(member)) { - this._checkForStaticMember(sourceClass, member.name.getText()); + this._checkStaticMember(sourceClass, member.name.getText(), expectDeclaredMembers); } }); } @@ -137,8 +132,10 @@ class Walker extends Lint.RuleWalker { symbol.valueDeclaration : null; if (currentClass) { - this._lintClass(currentClass, node); - this._lintInterfaces(currentClass, node); + // Acceptance members should not be re-declared in the derived class. This + // is because acceptance members are inherited. + this._lintClass(currentClass, node, false); + this._lintInterfaces(currentClass, node, false); } } } @@ -148,8 +145,10 @@ class Walker extends Lint.RuleWalker { * @param node Class declaration to be checked. * @param sourceClass Class declaration on which to look for static properties that declare the * accepted values for the setter. + * @param expectDeclaredMembers Whether acceptance members should be expected or unexpected. */ - private _lintInterfaces(node: ts.ClassDeclaration, sourceClass: ts.ClassDeclaration): void { + private _lintInterfaces(node: ts.ClassDeclaration, sourceClass: ts.ClassDeclaration, + expectDeclaredMembers: boolean): void { if (!node.heritageClauses) { return; } @@ -161,7 +160,8 @@ class Walker extends Lint.RuleWalker { const propNames = this._coercionInterfaces[clauseType.expression.text]; if (propNames) { - propNames.forEach(propName => this._checkForStaticMember(sourceClass, propName)); + propNames.forEach(propName => + this._checkStaticMember(sourceClass, propName, expectDeclaredMembers)); } } }); @@ -170,22 +170,19 @@ class Walker extends Lint.RuleWalker { } /** - * Checks whether a class declaration has a static member, corresponding - * to the specified setter name, and logs a failure if it doesn't. - * @param node - * @param setterName + * Based on whether the acceptance members are expected or not, this method checks whether + * the specified class declaration matches the condition. */ - private _checkForStaticMember(node: ts.ClassDeclaration, setterName: string) { - const coercionPropertyName = `${TYPE_ACCEPT_MEMBER_PREFIX}${setterName}`; - const correspondingCoercionProperty = node.members.find(member => { - return ts.isPropertyDeclaration(member) && - tsutils.hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword) && - member.name.getText() === coercionPropertyName; - }); - - if (!correspondingCoercionProperty) { + private _checkStaticMember(node: ts.ClassDeclaration, setterName: string, + expectDeclaredMembers: boolean) { + const {memberName, memberNode} = this._lookupStaticMember(node, setterName); + if (expectDeclaredMembers && !memberNode) { this.addFailureAtNode(node.name || node, `Class must declare static coercion ` + - `property called ${coercionPropertyName}.`); + `property called ${memberName}.`); + } else if (!expectDeclaredMembers && memberNode) { + this.addFailureAtNode(node.name || node, `Class should not declare static coercion ` + + `property called ${memberName}. Acceptance members are inherited.`, + Lint.Replacement.deleteText(memberNode.getFullStart(), memberNode.getFullWidth())); } } @@ -195,34 +192,25 @@ class Walker extends Lint.RuleWalker { if (!node.decorators) { return false; } - - // If the class is a component we should lint. + // If the class is a component, we should lint it. if (node.decorators.some(decorator => isDecoratorCalled(decorator, 'Component'))) { return true; } + // If the class is a directive, we should lint it. + return node.decorators.some(decorator => isDecoratorCalled(decorator, 'Directive')); + } - const directiveDecorator = - node.decorators.find(decorator => isDecoratorCalled(decorator, 'Directive')); - - if (directiveDecorator) { - const firstArg = (directiveDecorator.expression as ts.CallExpression).arguments[0]; - const metadata = firstArg && ts.isObjectLiteralExpression(firstArg) ? firstArg : null; - const selectorProp = metadata ? - metadata.properties.find((prop): prop is ts.PropertyAssignment => { - return ts.isPropertyAssignment(prop) && prop.name && ts.isIdentifier(prop.name) && - prop.name.text === 'selector'; - }) : - null; - const selectorText = - selectorProp != null && ts.isStringLiteralLike(selectorProp.initializer) ? - selectorProp.initializer.text : - null; - - // We only want to lint directives with a selector (i.e. no abstract directives). - return selectorText !== null; - } - - return false; + /** Looks for a static member that corresponds to the given property. */ + private _lookupStaticMember(node: ts.ClassDeclaration, propName: string) + : {memberName: string, memberNode?: ts.PropertyDeclaration} { + const coercionPropertyName = `${TYPE_ACCEPT_MEMBER_PREFIX}${propName}`; + const correspondingCoercionProperty = node.members + .find((member): member is ts.PropertyDeclaration => { + return ts.isPropertyDeclaration(member) && + tsutils.hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword) && + member.name.getText() === coercionPropertyName; + }); + return {memberName: coercionPropertyName, memberNode: correspondingCoercionProperty}; } /** Determines whether a setter node should be checked by the lint rule. */ diff --git a/tools/tslint-rules/noCrossEntryPointRelativeImportsRule.ts b/tools/tslint-rules/noCrossEntryPointRelativeImportsRule.ts index 558cc263a9c9..957d62bd2c90 100644 --- a/tools/tslint-rules/noCrossEntryPointRelativeImportsRule.ts +++ b/tools/tslint-rules/noCrossEntryPointRelativeImportsRule.ts @@ -13,8 +13,8 @@ const BUILD_BAZEL_FILE = 'BUILD.bazel'; * unintentionally and could break module resolution since the folder structure * changes in the Angular Package release output. */ -export class Rule extends Lint.Rules.TypedRule { - applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithFunction(sourceFile, checkSourceFile, this.getOptions().ruleArguments); } } @@ -31,7 +31,7 @@ function checkSourceFile(ctx: Lint.WalkContext) { return; } - const visitNode = (node: ts.Node) => { + (function visitNode(node: ts.Node) { if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { if (!node.moduleSpecifier || !ts.isStringLiteralLike(node.moduleSpecifier) || !node.moduleSpecifier.text.startsWith('.')) { @@ -54,9 +54,7 @@ function checkSourceFile(ctx: Lint.WalkContext) { return; } ts.forEachChild(node, visitNode); - }; - - ts.forEachChild(ctx.sourceFile, visitNode); + })(ctx.sourceFile); } /** Finds the closest Bazel build package for the given path. */ diff --git a/tools/tslint-rules/noImportExportSpacingRule.ts b/tools/tslint-rules/noImportExportSpacingRule.ts new file mode 100644 index 000000000000..816e628b9248 --- /dev/null +++ b/tools/tslint-rules/noImportExportSpacingRule.ts @@ -0,0 +1,37 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint'; + +/** + * Rule that ensures that there are no spaces before/after the braces in import and export clauses. + */ +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile) { + return this.applyWithFunction(sourceFile, walkContext, this.getOptions().ruleArguments); + } +} + +function walkContext(context: Lint.WalkContext) { + (function visitNode(node: ts.Node) { + if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { + const clause = ts.isImportDeclaration(node) ? node.importClause : node.exportClause; + + if (clause) { + const clauseText = clause.getText(); + + if (clauseText.startsWith('{') && clauseText.endsWith('}') && ( + clauseText.includes('{ ') || clauseText.includes(' }'))) { + + context.addFailureAtNode(clause, + `${ts.isImportDeclaration(node) ? 'Import' : 'Export'} clauses should not have ` + + `spaces after the opening brace or before the closing one.`, + new Lint.Replacement( + clause.getStart(), clause.getWidth(), + clauseText.replace(/{\s+/, '{').replace(/\s+}/, '}') + )); + } + } + } + + ts.forEachChild(node, visitNode); + })(context.sourceFile); +} diff --git a/tools/tslint-rules/noImportSpacingRule.ts b/tools/tslint-rules/noImportSpacingRule.ts deleted file mode 100644 index 8872ae725c7d..000000000000 --- a/tools/tslint-rules/noImportSpacingRule.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as ts from 'typescript'; -import * as Lint from 'tslint'; - -/** - * Rule that ensures that there are no spaces before/after the braces in import clauses. - */ -export class Rule extends Lint.Rules.AbstractRule { - apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); - } -} - -class Walker extends Lint.RuleWalker { - visitImportDeclaration(node: ts.ImportDeclaration) { - if (!node.importClause) { - return super.visitImportDeclaration(node); - } - - const importClauseText = node.importClause.getText(); - - if (importClauseText.startsWith('{') && importClauseText.endsWith('}') && ( - importClauseText.includes('{ ') || importClauseText.includes(' }'))) { - - const fix = new Lint.Replacement( - node.importClause.getStart(), node.importClause.getWidth(), - importClauseText.replace(/{\s+/, '{').replace(/\s+}/, '}') - ); - - this.addFailureAtNode(node.importClause, 'Import clauses should not have spaces after the ' + - 'opening brace or before the closing one.', fix); - } - - super.visitImportDeclaration(node); - } -} diff --git a/tools/tslint-rules/noNestedTernaryRule.ts b/tools/tslint-rules/noNestedTernaryRule.ts new file mode 100644 index 000000000000..d39ceb64b40b --- /dev/null +++ b/tools/tslint-rules/noNestedTernaryRule.ts @@ -0,0 +1,37 @@ +import * as Lint from 'tslint'; +import * as ts from 'typescript'; + + +/** Rule that enforces that ternary expressions aren't being nested. */ +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, context => { + (function walk(node: ts.Node) { + if (!ts.isConditionalExpression(node)) { + ts.forEachChild(node, walk); + } else if (hasNestedTernary(node)) { + context.addFailureAtNode(node, 'Nested ternary expression are not allowed.'); + } + })(context.sourceFile); + }, this.getOptions().ruleArguments); + } +} + +/** Checks whether a ternary expression has another ternary inside of it. */ +function hasNestedTernary(rootNode: ts.ConditionalExpression): boolean { + let hasNestedTernaryDescendant = false; + + // Start from the immediate children of the root node. + rootNode.forEachChild(function walk(node: ts.Node) { + // Stop checking if we've hit a ternary. Also note that we don't descend + // into call expressions, because it's valid to have a ternary expression + // inside of a callback which is one of the arguments of a ternary. + if (ts.isConditionalExpression(node)) { + hasNestedTernaryDescendant = true; + } else if (!ts.isCallExpression(node)) { + node.forEachChild(walk); + } + }); + + return hasNestedTernaryDescendant; +} diff --git a/tools/tslint-rules/rxjsImportsRule.ts b/tools/tslint-rules/rxjsImportsRule.ts deleted file mode 100644 index c942e6c19701..000000000000 --- a/tools/tslint-rules/rxjsImportsRule.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as ts from 'typescript'; -import * as Lint from 'tslint'; - -/** - * Rule that ensures that all rxjs imports come only from `rxjs` and `rxjs/operators`. - */ -export class Rule extends Lint.Rules.AbstractRule { - apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); - } -} - -class Walker extends Lint.RuleWalker { - visitImportDeclaration(node: ts.ImportDeclaration) { - const specifier = node.moduleSpecifier.getText().slice(1, -1); - - if (specifier.startsWith('rxjs') && specifier !== 'rxjs' && specifier !== 'rxjs/operators') { - this.addFailureAtNode(node, 'RxJS imports are only allowed from `rxjs` or `rxjs/operators`.'); - } - - super.visitImportDeclaration(node); - } -} diff --git a/tsconfig.json b/tsconfig.json index 989bd381e325..7033629c5210 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "noImplicitAny": true, "noImplicitThis": true, "skipLibCheck": true, + "strictBindCallApply": true, "target": "es2015", "lib": ["es5", "es2015", "dom"], "types": ["jasmine"], diff --git a/tslint.json b/tslint.json index 7ea3eceb0516..2d016dedf5c3 100644 --- a/tslint.json +++ b/tslint.json @@ -108,15 +108,15 @@ // Custom Rules "ts-loader": true, "no-exposed-todo": true, - "no-import-spacing": true, + "no-import-export-spacing": true, "no-private-getters": true, "no-undecorated-base-class-di": true, "no-undecorated-class-with-ng-fields": true, "setters-after-getters": true, "ng-on-changes-property-access": true, - "rxjs-imports": true, "require-breaking-change-version": true, "class-list-signatures": true, + "no-nested-ternary": true, "coercion-types": [true, ["coerceBooleanProperty", "coerceCssPixelValue", "coerceNumberProperty"], { @@ -181,7 +181,10 @@ "\\w+Rule.ts": "camel-case", ".*": "kebab-case" }], - "no-unescaped-html-tag": true + "no-unescaped-html-tag": true, + // Ensures that all rxjs imports come only from `rxjs` and `rxjs/operators`. Also ensures + // that no AST utils from `@schematics/angular` are imported. These should be vendored. + "import-blacklist": [true, ["^@schematics/angular/.*(ast).*", "^rxjs(?!$|/operators$).*"]] }, "linterOptions": { "exclude": [ diff --git a/yarn.lock b/yarn.lock index 5e9ebe4b58cc..9726329d8bce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@angular-devkit/core@9.0.0-rc.6", "@angular-devkit/core@^9.0.0-rc.6": - version "9.0.0-rc.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.0.0-rc.6.tgz#19313faebb932b46545c48b325e4504300c55fe8" - integrity sha512-tdr//+Wjm6OgxbtO5pzeKNf7P6X8MtTor4uyQfkaTrAFpf/LuewDTrzc6z1PrNqYASxuSV576SJrRmmralfr8w== +"@angular-devkit/core@9.0.0-rc.11", "@angular-devkit/core@^9.0.0-rc.11": + version "9.0.0-rc.11" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.0.0-rc.11.tgz#9e69545eb21284a573ad78e4c33003f2ea25afd5" + integrity sha512-ki7Sln+mQdCctJNBalzy70tiFn2hOCY2Yyte8B0xKWVHnofZySvG+ANzoLgodnKFOBH18AQy35FhgzZM++N9tQ== dependencies: ajv "6.10.2" fast-json-stable-stringify "2.0.0" @@ -13,41 +13,41 @@ rxjs "6.5.3" source-map "0.7.3" -"@angular-devkit/schematics@9.0.0-rc.6", "@angular-devkit/schematics@^9.0.0-rc.6": - version "9.0.0-rc.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.0.0-rc.6.tgz#10e2214d828dc3b00c7ab41251b8fbb57039ecba" - integrity sha512-hxUUq2vxj9Z8cqlF3hzquJsudcGAlDGcn6dz8K+1N6gvHNIu9FgIi3rzwleDsATnMdiZxHq+cuQLbH9qFbkFaw== +"@angular-devkit/schematics@9.0.0-rc.11", "@angular-devkit/schematics@^9.0.0-rc.11": + version "9.0.0-rc.11" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.0.0-rc.11.tgz#e0d4d271d8d783ebf05eced576262f20e6c3562c" + integrity sha512-aJqOLzsoAkVj3AVTf1ehH2hA9wHHz1+7TTtfqI+Yx+S3jFyvGmnKrNBCKtMuIV5JdEHiXmhhuGbNBHwRFWpOow== dependencies: - "@angular-devkit/core" "9.0.0-rc.6" + "@angular-devkit/core" "9.0.0-rc.11" ora "4.0.2" rxjs "6.5.3" -"@angular/animations@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.0.0-rc.7.tgz#ed1aadba110ff086e4a522e802ea5b6d499a7ad3" - integrity sha512-N2JnT9XHu2wOo4zzjK+Aiu0Ow5NYXLIp2XSHPH31ZOzhNj5eSFZJr1Sjk0bg6oXDqCIyfcAUQnkwYKbe2NyeFQ== +"@angular/animations@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.0.0-rc.12.tgz#13e4c594f71aac5c3457342cea0f363b048cc4e6" + integrity sha512-fsTxlCptaVlwGD8B2H3ke2bRLKzSDCMwEWxf9O/rjQhUSaIVRMGMTTy8rW8OvxF2ysZatU+ademROxBaUsO/sw== -"@angular/bazel@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/bazel/-/bazel-9.0.0-rc.7.tgz#32b8c99885f6103ee2532e5a8ba6969c7142527d" - integrity sha512-AhIeNxUtJK/3zsUi+G5P/+sd21rGS1GIeoMqvvBuKhcEJhNpJP1VSBtUkYuPxuYQqwDUik9fvK7BMgCjoJ2pxQ== +"@angular/bazel@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/bazel/-/bazel-9.0.0-rc.12.tgz#0841a229b13793af4b2fe64895355764900b3d82" + integrity sha512-R90bZhEh4oBFi6P6TjDPhKWfGWsp6xYUUh26Nxa3IhSnHwrXTB9EIUIhu173Q+YOZyNFsyk4fppL3GDeu5/GkA== dependencies: "@microsoft/api-extractor" "^7.3.9" shelljs "0.8.2" - tsickle "^0.37.1" + tsickle "^0.38.0" -"@angular/common@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.0.0-rc.7.tgz#7258b0f00d18b1f5c8f12c7b5bce4cfb9f58957a" - integrity sha512-2WGAizPJ6Yst/sP0boiYoGvMojcUKxqlRdn8ineCFCZlgkYZ/qYxY5R0gXwN1DPHVNKrNk1LDs11DYoGU25Htg== +"@angular/common@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.0.0-rc.12.tgz#21fcbc7bc9ce94cbdedc0f5aa9713c35311afba8" + integrity sha512-bSNwXjaSH52slnTVqNEAXz0H+VCBLjc5Qzq4e0Urfr4qacE6kKD0dYnXZH/iGeaZHL/kw0nl8c0Pe1WCzoF3kg== -"@angular/compiler-cli@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-9.0.0-rc.7.tgz#0cba2650b0e3c1e03f51e03399c6f8a8fc60ab1f" - integrity sha512-341PP6Ewjt7x9l9tut5a7273jttAL1aY++YCy3vX6QYwIkxKGG3ZQgSfxa8M4tXwN5+wH0JkZWoSPVAmfRtsRw== +"@angular/compiler-cli@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-9.0.0-rc.12.tgz#11787006ba26a8aaef2ad3e09f0b94047ab6b1b8" + integrity sha512-vA/UpUWttJALXnmE+eJ0x2LAi+Gm5P3l1uj0gdoK14OyARUWhm+Oc0Dj8e5mXpy/pIVrsL4S/FL4tsNUSGynBQ== dependencies: canonical-path "1.0.0" - chokidar "^2.1.1" + chokidar "^3.0.0" convert-source-map "^1.5.1" dependency-graph "^0.7.2" magic-string "^0.25.0" @@ -57,48 +57,48 @@ source-map "^0.6.1" yargs "13.1.0" -"@angular/compiler@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.0.0-rc.7.tgz#395f4fc4284b2651a24a4c1c95e31e57296c2b14" - integrity sha512-kwX6475iuFEWcQc62mgd9Dq1lGMvqZXxEc0EZqsewi9Dpg/rj0ImXeYOTtIKFrgAkg03ahADBjtCComB0eKZgg== - -"@angular/core@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.0.0-rc.7.tgz#842f37485244b77b1c104cadade2c82fb4cb3fac" - integrity sha512-qn2lYImZNVzWOz8dR32Z9TTcW+nYX31H8Ckzn++xbL0x0W9iKg13GaNfaVJqnZI1pnzpRzzJdWXf5xQQQsKQsw== - -"@angular/elements@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-9.0.0-rc.7.tgz#9878944558bd07b34304bacac4e9233f4329df00" - integrity sha512-09jf3wT2O//47uOc9cwF55FRPw7BUS+eMZbmmtfK4rQQfp4sDTqSXcU1oj22+HYH/wfQ1O0NAFE/uUi6knMzUw== - -"@angular/forms@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-9.0.0-rc.7.tgz#0b0bfd3d60c3456b4eb0f7bf1dfeec97d04458ba" - integrity sha512-hY/Dn2zg/yQb8Qk7a8RiwGJrankQAJssV4JJZSBuVGgtbdhdKv1+u9xNVcqYPlY+WuBhyvU+WoRWlooMv3c1AA== - -"@angular/platform-browser-dynamic@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.0-rc.7.tgz#9bfe1a8776c8bcae95f42a7dd003aa908c9f3a09" - integrity sha512-r5qwnEotJm0RXNqxa7GcW9+RQMGCYMzGT/yr6CTQmLsn5Gn0ljE8DLSTpmNDIGTfwqIm1E9VIApASRLv/maeCQ== - -"@angular/platform-browser@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.0.0-rc.7.tgz#992e70f31ac0c4ecfb99f57ecec2d8eaf0bba7d7" - integrity sha512-3xCVDX7QzK1V2XGwitakl/1Ekt3A0RfFqxffVa40iQH7shlAQ7SYDeTSj2Mtaz5QoqbkzLT9ZCrl9KtVllaojw== - -"@angular/platform-server@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-9.0.0-rc.7.tgz#93336258d26fa2459deff8595b943c8f2840044c" - integrity sha512-BG91B85KIy5U/+lMFXCmt9Fav8mSzURayF4rGyBAsOK5EreAJogntUS6zhABukSIyQpEYVA5nIXfx19l27nfXA== +"@angular/compiler@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.0.0-rc.12.tgz#d2a2f3b180daf697316b4bfcd8a838e36b6760da" + integrity sha512-mX9VavzWr1b4URP/M287I73ibgL+T+eU7xZg56FEvUYa7km9W6jmM2xomvaumPlwGtZfw2HKPCgSimOIA6eP8Q== + +"@angular/core@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.0.0-rc.12.tgz#c0eb402910ff4c6f50eeb8e9a539c0c90aff36b8" + integrity sha512-MRPk98VHIJqpHoeHBtYtXGoGs+T1DpRAUpSzl7mprdKgfBKoFhtgsrNNj3JSD1XYLPamSH7vrKYBsP+E0xu88Q== + +"@angular/elements@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-9.0.0-rc.12.tgz#038574b3b748a153c86af6f95ca8bf0b483948d1" + integrity sha512-BVVRiVfB86AP3w30mHqwIGNHryiNLMMlaQVFQfTYvh2203283tPU6Gao6/k9J3XEV4lC7oQFNJW+HC5HG2nhjw== + +"@angular/forms@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-9.0.0-rc.12.tgz#004b4597970784e19bcde8433f23a999a75127c2" + integrity sha512-n4ch/2NJf4MS+oybQ77tQFMS6/3Gjx8PqzFxRf0rXuNEKbs/5Nd+KxKuPV5jbKJTFDA+PLXdmq0Kb+l2h/LAMA== + +"@angular/platform-browser-dynamic@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.0-rc.12.tgz#44e6ec249daa3f97ff366f3110996c598d2a6fe4" + integrity sha512-dI9fjeXQUvY/MQpMvsbx0nyGL0VPPtad0xUH/VqN4qzuYTGs7U6wtCxcjfAM4lNDdZ2rZ4hpm6db6tJJ7mo42w== + +"@angular/platform-browser@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.0.0-rc.12.tgz#dae2ba271da9e77398d0ff503a1397219b678268" + integrity sha512-Un+p/8tDp4GlfRhc+uUlsT4pfDRWJk3ndQ+MmpTWV0FFQUnomtRiips/rVkFUbghDzKLHnStmfHG+qyrSjknCw== + +"@angular/platform-server@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-9.0.0-rc.12.tgz#1387f89761d10d8f947dd8bfa87abeb00c13e53f" + integrity sha512-pfwFwxDWFMfFLEoNiHuWA92E7stqpZZ+rvKDmdy2H3Y6TRW2Zs+bfcxPvCWM/LVpS6q4yVtFYZXFK4f1WdssMw== dependencies: domino "^2.1.2" xhr2 "^0.1.4" -"@angular/router@^9.0.0-rc.7": - version "9.0.0-rc.7" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-9.0.0-rc.7.tgz#9c042e8832df1c20bcbf16d0994df6ace68699db" - integrity sha512-6kviTBp43UyYj/Ah4qMhoga5YVHTVf3fmvbA4ETfTZAV0IEiPahxhn3FJ5Yal9Lk6Fc2kGmDvuDJgbn7gNbppA== +"@angular/router@^9.0.0-rc.12": + version "9.0.0-rc.12" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-9.0.0-rc.12.tgz#62b09436ca331a9171f9df34fec34e2f8a055330" + integrity sha512-8rJm/NKiU6Rpj+KtY9NedJNFJgN85r107y5LtClCJ3QMzOuoG5y6UYaba7jZfKFzOMiK11yQmKj63LMNz4wEjQ== "@babel/code-frame@^7.0.0": version "7.0.0" @@ -189,6 +189,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew== +"@babel/runtime@^7.6.3": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf" + integrity sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -282,31 +289,31 @@ resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.10.3.tgz#2e2b8a1d3e885946eac41db2b1aa6801fb319887" integrity sha512-v1nXbMTHVlMM4z4uWp6XiRoHAyUlYggF1SOboLLWRp0+D22kWixqArWqnozLw2mOtnxr97BdLjluWiho6A8Hjg== -"@bazel/jasmine@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-0.41.0.tgz#4e9c1622b6ceacdcf12740098d8c99fc3cff6bfb" - integrity sha512-vbZaTx05Ab5ZbJ1TDHjwWvtrDYBh4miXVJBdFeSZtquLQ0FQb9iNdMTuDHkEF9aq6nqG+rnjUoQjjmDLC/uRkg== +"@bazel/jasmine@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-1.0.0.tgz#12aa82c0e4b7b5658e9e496b73c0cf660df2b0ae" + integrity sha512-cheQWMYs0PaS4kYwJC9tmAiyd0tLbPNcgoM+S8plNUp5k0gUVosCvQ0TOrxvRlKEY0ni1hdn9owPx2rUO1J+Wg== dependencies: jasmine "~3.4.0" jasmine-core "~3.4.0" v8-coverage "1.0.9" -"@bazel/karma@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-0.41.0.tgz#1ad3b48d8b5cf1e08ed266872c18344c8bd63e94" - integrity sha512-L23VC9EUFmrMCYJAT9qPnyezM5IErCJ8URpmXfRo/7IUJ04/l4vQeiyi4iNY7+zw9lbGf2I8seGaPFJrFtxdoQ== +"@bazel/karma@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-1.0.0.tgz#6f1e31bc2cb698e50d76f43ec9f1ee095d8e72b8" + integrity sha512-esfsHRVWVcp6OyTk/QI1sBV7bcygFiXY6LUMPLPYqVZkQvJ0x+/L1weiwuRrqFf+FCwCajhYjwiR6S9kX2Z9LA== dependencies: tmp "0.1.0" -"@bazel/protractor@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-0.41.0.tgz#56075a657f236602bd762e712dd42c514baede0c" - integrity sha512-sQtAYheBOEcX32BWNyRj5VssCrMbPzEun4x9QPWnoxtBebkxeuZplRjbaoc+PFdVYys+ixwAqTkS49qlV4Ng1w== +"@bazel/protractor@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-1.0.0.tgz#b77fde3bb53a008c330c13a953a0bf93aa06b5f1" + integrity sha512-8kGuM3NkFfokjzKBlYxK9rUwJO/ICnYTHC567KJk6T4pjm4kfyh6iVI3Df3K+ONRCEYmiuZrQ37U06lzMMGN9g== -"@bazel/typescript@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-0.41.0.tgz#b767c4972cc42fd76c118f21e83e98860afdc249" - integrity sha512-i+PXf5BR7aUEA49V+D8OatHonNCDm2MhtjFwEJ7yKKLTPEm4riHWSDASNuOi4iAAe+EB8eKe1Fxh8iSjh/hm4A== +"@bazel/typescript@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.0.0.tgz#2c7318b9a3aaa730f282b094ddf41dd16fda7b5a" + integrity sha512-caNOKz7EjVMgbedjVTreao9++9Sb9oYlU2nqDOMIK8MyoOUQvgGQWhFwF65XXGSb79Tzv8kaFQskoaH/iAs4ng== dependencies: protobufjs "6.8.8" semver "5.6.0" @@ -318,537 +325,535 @@ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.3.2.tgz#a92dc544290e2893bd8c02a81e684dae3d8e7c85" integrity sha512-ZD8lTgW07NGgo75bTyBJA8Lt9+NweNzot7lrsBtIvfciwUzaFJLsv2EShqjBeuhF7RpG6YFucJ6m67w5buCtzw== -"@material/animation@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/animation/-/animation-5.0.0-canary.50f110a6c.0.tgz#208f5e9e8d9818097f64c73a8820f87a42817fbb" - integrity sha512-f+eTMEoOLtwLhwu8BHk4G6C/ldtSIzVJpzdxtbRHM4c+1jKoiPtHzvV9V6BfFs/k/vGJlogHFFreAO8VdSF51Q== +"@material/animation@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/animation/-/animation-5.0.0-canary.80a4d326f.0.tgz#0e004b8c75f1b201d9130bc43c9fabb0a6ea8db3" + integrity sha512-QeNznXYDBy/2wj8CQmZCK9vudbwgi0Taigexr9mDy621NYVnmlp5J+/bN671i1sdRWVjNyXb5tFeu+9XF8VBbg== dependencies: tslib "^1.9.3" -"@material/auto-init@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/auto-init/-/auto-init-5.0.0-canary.50f110a6c.0.tgz#01beaffc0351483eff65915eb754ee829af940f0" - integrity sha512-9s7Iznqg1Xb80D3NG5C+WUdE3rR2Z92BcUXRnwpymgLWKhT3e0TU02fjx4dAgrt5avNRWx4/+3vmo37BUD2cRg== +"@material/auto-init@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/auto-init/-/auto-init-5.0.0-canary.80a4d326f.0.tgz#f9b09290f0f0e6aac8619fafe7fbad1c410edf82" + integrity sha512-TXX8Ks1fnEUHkVw8jBibsTt0IiXtIIltGcbOr6EvVy6cLtV2yXBRhJlC9FfHWGE61y55VWFDB9iCNRRUS9NxHw== dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" + "@material/base" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/base@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/base/-/base-5.0.0-canary.50f110a6c.0.tgz#22ae3315522a85b8caab71f6718e49889caa20aa" - integrity sha512-lPlOVA9/gYBf1CEg/nIrJKBm0fp4b+0mDUTgjFqfpPaCQqf0o2DppA2ZwvHHxzdfrJ6RRepb7qlTpu8bIJH+WQ== +"@material/base@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/base/-/base-5.0.0-canary.80a4d326f.0.tgz#2059d4ff00f512e91f194ccda3704dceb1a38af3" + integrity sha512-I9Hruzd1wRBpiz8ftKgfJ8l9+LhLmmOV1oA9Xk7/EyeZvqPAuOg+9kpz7yvOPdJx3FaXsUZ1aMrbvSiwBhtRXQ== dependencies: tslib "^1.9.3" -"@material/button@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/button/-/button-5.0.0-canary.50f110a6c.0.tgz#07fcd9b6982f0700d94ed1cca1e66dc68acafc4b" - integrity sha512-DaCPtdkfNu1WO3Ae5H6rl/C0Uol4b0Si164OSUwfbhutsowx6ow6m6771QYfuRE2c348yOamTMih+GQ+X0fCBg== - dependencies: - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - -"@material/card@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/card/-/card-5.0.0-canary.50f110a6c.0.tgz#b4f996f1ea1195d38da639afff6f826b0e5b9966" - integrity sha512-yxpky4QMfj/yaGtRFhr3+f2DDKUanHl+q3iyY0MxhGTDLrtT46h5Ye4KCc3TULP7i9u6rLtTAS8LGhUjUgE/fw== - dependencies: - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - -"@material/checkbox@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-5.0.0-canary.50f110a6c.0.tgz#c884fcc16a65a39a343e70a64922118a9c4a3f63" - integrity sha512-gX0TRYYC8iDku9dEWbxrDSyFX+Y2Xqa6dXOxWekTSzWEUDDy0AEOEiTMPBPqNUd2wW193V1RdvxkyBf4ll7GSA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" +"@material/button@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/button/-/button-5.0.0-canary.80a4d326f.0.tgz#205309f9824d185564f3ad5e412986811fe0b862" + integrity sha512-w3ze+noNqjX00Qo8DQj4hNyDgjv0/wes+HV2k8Z0ezz5WAyplCTI00yDk3NAUw/iXTOBwFT2FaSwVVj6YyFlEg== + dependencies: + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" + +"@material/card@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/card/-/card-5.0.0-canary.80a4d326f.0.tgz#f2c4a31077aec0d7418d0f33e7f486150d254b64" + integrity sha512-DAh8QruWlBuM6uQqbC3RXnbuewzraC7i/hk+tmhClSl+ycyCQd2bbwqQVJMXR0vldetF5jE7a+nKa1DZsn23og== + dependencies: + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + +"@material/checkbox@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-5.0.0-canary.80a4d326f.0.tgz#45b766a4a04d24afbe31fbe63d7517e1e168958b" + integrity sha512-KSog5YdKJCwYPoRI3E5zP7A7KNx5bPxO6as7PNNE6xced2l1mXwQkJNquvMKZjjfLWPAP5z9yuTv+zySD1LYyQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/chips@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/chips/-/chips-5.0.0-canary.50f110a6c.0.tgz#457d29368678e4af880dc33b8f337e28f387a35c" - integrity sha512-3P7IxrgH3vVWCqx+N5MaPwfc/2m04fDvL6sZYUqGBHkwraeJv2vKsZ6vNrRTVu6K+wGSK5tyeIWhePf+GE0SNQ== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/checkbox" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/chips@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/chips/-/chips-5.0.0-canary.80a4d326f.0.tgz#e523a1d6dee717fb753a343fdebda1194fc05804" + integrity sha512-XytliiVRNMQ17LEpFVHlifDjx1NrnytjvMlkBoC3FXnPLIK88Tx4oSTAzfIzn9vt4l+v/UwaVoS8b9iaQmonTQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/checkbox" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/data-table@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-5.0.0-canary.50f110a6c.0.tgz#35d220477aeb87dc373f060697b2a47432f6fa13" - integrity sha512-9FTSn+7ME7UIp+iuJHj4o1WdAdbfyQRRHQYAGwWkSAHgLNvi2kDenUTBeNuDh6wfKcRXEXmmLvISY/wXmEWOHA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/checkbox" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/data-table@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-5.0.0-canary.80a4d326f.0.tgz#e3ca7eee42a28a4fbb9474a5737853fdd2e4dcac" + integrity sha512-n3cnkMezHq9Rb3VtMpbNBSnPsZnGUrRiLitH8UsgRIoyUT2F4nfuYH4+W+XwPJ8XAqbRdNbL5IqOj+SoxEH5WA== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/checkbox" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.10.0" -"@material/density@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/density/-/density-5.0.0-canary.50f110a6c.0.tgz#a728e7e34dc80f584be452c6571f1df730bbe950" - integrity sha512-3za6eir4hkEdGVizayBHlTKN5XsFoVivUv9ph6NnK6vy4mZXxql0JHfApA0uOXERAr4JnBfn/8MRl3u1KgVyaA== - -"@material/dialog@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-5.0.0-canary.50f110a6c.0.tgz#ad79a7a87ec6ccea3e14e5695c67f090aa1ee61b" - integrity sha512-GsW6nyh2MmYScwvQpNlswaXKiBT4SHcg00r5JsgWykUe0p9qDA1kY9J1vjIB6W5TaVnGTyoDT9WvgjiO9fzEBA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - focus-trap "^5.0.0" +"@material/density@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/density/-/density-5.0.0-canary.80a4d326f.0.tgz#abc10bc2991f905ae524759391658bc52411d8e9" + integrity sha512-jTv7S/+R0bYDYAvDsgRtC8v8epfg0NSbWQG92M8TaMmKovASIy+9Ear3KIDgjCplZ18HGPBNLdf+pHqu2f+hIg== + +"@material/dialog@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-5.0.0-canary.80a4d326f.0.tgz#b641f3e37b7b9b88327a43e195296935611de6f7" + integrity sha512-6Ieu3d4LTWE/IiXKOUzj6IqhnrtwLHu6IjOi07rjxdUlqI1Vp3zFz0L8sjavsbROmxsMd4s4NdXniUL+fUkFnA== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/button" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/dom@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/dom/-/dom-5.0.0-canary.50f110a6c.0.tgz#75e2c1ad32f04437b745eb6a8e10f6be612cdfaf" - integrity sha512-wlmZZZzUfTeHhsZDYLXwqkYvDLquiO4FZ6ETvNJAvz24McmRhBagZSSWYlHSK+dHAJKIfHo0Wt4JJ2VZsEjKnw== +"@material/dom@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/dom/-/dom-5.0.0-canary.80a4d326f.0.tgz#98ea18ae340dfe7577b78107c00468355bef124a" + integrity sha512-lYbPCfYz7pmFc0bt6RC1OdrQd1pQp1f57VAlq6paAgqLZnT01Vwqzi9IN9vZYodk3P3y6yQXOGnk/1MRB6jV1g== dependencies: tslib "^1.9.3" -"@material/drawer@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-5.0.0-canary.50f110a6c.0.tgz#44125ca24d72d1d16c6d1de6626b2d8108e44297" - integrity sha512-nAX4PzXUR1kk6vjdL7WNXBQltXyOQe/Zvo4MhCkA7QuSY9NCAbPeVK8C/fLVAJEkqXalYqtGI9udeKtx1u+MMA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/list" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - focus-trap "^5.0.0" +"@material/drawer@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-5.0.0-canary.80a4d326f.0.tgz#044a85850098fe5d46adc0e9e88709a7def7c94f" + integrity sha512-Me7Rg0HERGsTMQI6eH7QjvybaRjgwYC93oVmaCGHDLXdBXF76p/OvAt0BrDCJtZgCrPStouWTsG4gH8+zbgKuA== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/list" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/elevation@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-5.0.0-canary.50f110a6c.0.tgz#697ce92fe736b36679d0f53300b4cb91463db7d3" - integrity sha512-1lfZHIMiqpGtANEIP0Tru8ezseM7KeOpomHBpbkuAIJtP3zceQ1Qkr/cM8lZ8oWT7KSJSm3dENfIdsx/o4lI9w== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - -"@material/fab@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/fab/-/fab-5.0.0-canary.50f110a6c.0.tgz#ca1065c53c3db9b217ceaa073c3bed2613cfb2c9" - integrity sha512-48mXijii92ZDtwZb5Cpa15XMVt/02DD9e/3pOXt457F0krE66RWgQ1Gb8IsVC8ZjcVQIcoQvbu4s/Qit5nD1PA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - -"@material/feature-targeting@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-5.0.0-canary.50f110a6c.0.tgz#f59e52c23cc519d33dfcde91121ff6f3f5ae7b0f" - integrity sha512-W2mwHoW3XI2xMhNhQcklUoOmjTg1ovCjMDZFF2V5Pt7hedf41N+1yz8E4eT9LUDLnCqMqXDDMRvq0hRn3MRkOg== - -"@material/floating-label@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-5.0.0-canary.50f110a6c.0.tgz#80ae48c19080739988efc61a7dcb0a168cfc1c1d" - integrity sha512-7qDSPepiamBYCF2tFmvZd0yDbsIrT59iB/LOR6gAFBBhZqa91KMw+MhgnFv/6W3OZLyX1PPh65n5CvvjW/4WfA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/elevation@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-5.0.0-canary.80a4d326f.0.tgz#bbfa7f2aa1996a09dd8ba15cf78e4d0f21c523d7" + integrity sha512-iWoGrluPbTgpc2zu0AkrWSMzHc0kmY1L/6G8PYI1cl6CW7SLBC0NEJUfTdZXA2mWsCAQ9iIcgd6S28WSE2Phvg== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + +"@material/fab@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/fab/-/fab-5.0.0-canary.80a4d326f.0.tgz#1d8d6da367bc3adc5aa78932cd1a6ca0a84b4ba7" + integrity sha512-hJrKIRDz5Rk//sShi7yqyWv+zF8a0s/gUzCpbz/EvONS7bL6WU1/GVHdMEmlTzE2xLZDQ9Bjw2zjJorjdxkk7Q== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" + +"@material/feature-targeting@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-5.0.0-canary.80a4d326f.0.tgz#fd21c258ccd86e70aa93166a19b0429dc01b9fd8" + integrity sha512-yTQhT3rzEfHX8bmkEtWOP3G3KZ1fZ5ufwIcROAvP48m+GD3KSvelyAiKUnf5MZE4EV4R27XnAgumf79WK9DFfw== + +"@material/floating-label@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-5.0.0-canary.80a4d326f.0.tgz#60992d7e99830c3ee6bba82d8b35e5c688d650b1" + integrity sha512-BBmNRTVk4sPey+zYQux+9n3a+Kprh9Z0d57bLFRD4deqoCZSNA5qAOpG+qP4zHnnUnZWn8eSxDWbR3L0mkEPmQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/form-field@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-5.0.0-canary.50f110a6c.0.tgz#2be6c7225ba3ff4abc02eb408d5ae15f0be56863" - integrity sha512-eM9tGvLBKEBJeVuPqBd0qygY7vRoDlSDrh6YXjQxuKc66nFADSqOdmgPro+MieGlPp8OXBFebxzfNdohNeV9Ww== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/form-field@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-5.0.0-canary.80a4d326f.0.tgz#68139f6df5dd5af17fe0a9a2f243a84a9fa9d85b" + integrity sha512-yvLCTQckO0TPVphNlaDUB31iBfjGdV4fmHu6ieT7j5MSX9SGxuJqHzoPnwZVIrt7drfyiUeovoiArHpn3QWODQ== + dependencies: + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/grid-list@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/grid-list/-/grid-list-5.0.0-canary.50f110a6c.0.tgz#c80ffbd4a0ae53f8bd7bee37065eee794dfcde66" - integrity sha512-DWga7NtpTMUr5TRJCxlLJCsp7bxgSMTpOG2Ws3S3RSXdzFJohLV9lLPbfaOUpc6X0zGzysO9wfQDwJqV+JcMrw== +"@material/icon-button@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-5.0.0-canary.80a4d326f.0.tgz#cb05b0bb9a9724142f7fc22692edb5ef79dd6ee3" + integrity sha512-pEe8BHnMfKFSbq/v88g+2FC1LcYLGYl8g/IBizjOCj7dlQMH3FvlH33C2NRXngt5HJmq+KZg3v1BRHiIp5WkoQ== dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/icon-button@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-5.0.0-canary.50f110a6c.0.tgz#09c984a0dc0ff22bf22a911fb702ad7140f9bb02" - integrity sha512-vD+X1OErv70zAaRrfJCqaZyg7Xz5qRvXB5WvJdS6hJeM7jfF0UmE0o2J/vRkawOLufODeL3Ssq45gzWy9tZDew== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" +"@material/image-list@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-5.0.0-canary.80a4d326f.0.tgz#0d3f6fec73e5a6c6755f662b5a1d394a9e4b4bf1" + integrity sha512-0a8fld8LK5Bu2mPw88g2U/C6/5Z4oYUQkRonCo5u8eKHiF0YrFQcMxHkC09B1ReY4JYWvgAQ7qAbB0vfflzUAQ== + dependencies: + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" + +"@material/layout-grid@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-5.0.0-canary.80a4d326f.0.tgz#0a1b2216de01a24bcbd81d55a74eacf80096d291" + integrity sha512-H9dj6thKwVStsfMiVQf4riqB/H8600XvYdyL1/P6p6BetGOfLqqsC3vzSv9tYWYF4D1T4qZjQLk131ILkPtylg== + +"@material/line-ripple@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-5.0.0-canary.80a4d326f.0.tgz#8a606f9b165ac26518f2d7bd2c64bb3f00bf8fd4" + integrity sha512-v6sgkEzVgSZuvLBsjAoGemF2kep0K+nbPA4AzHapniob34v4XF8Y66eRQNMAQcuMv6Q2UcJFjNV20pKFZteYeQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/image-list@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-5.0.0-canary.50f110a6c.0.tgz#09a8bf63536a5503420e0946d801be8e3bdc9b83" - integrity sha512-Qsf/05yVbGsMMGikZI61B+3fN9j1PVHJocj4sOyi7piE4dcB0f7899kIx+MJFdNhbHWBUfR5POTRO16/lTDshg== - dependencies: - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - -"@material/layout-grid@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-5.0.0-canary.50f110a6c.0.tgz#4d75cbf2bf9ff54dd34b33c8d7db3f97ddb1e0c4" - integrity sha512-nADp2CNTV3HBSjgxt6vzXaR2O/k4q4NnwVvDEvC3M00/u/oMUZwew5ndzY1UZ+WOQjOo+Yeq4cB/hWEhi/ROBg== - -"@material/line-ripple@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-5.0.0-canary.50f110a6c.0.tgz#46162b98926f7c48833a3060cc7ea3ded9f64159" - integrity sha512-qrI1/pWemLrUCOZS24/ltGdYDsTdxqzd2MmNA68Zpqp8uXkYOeKzbdYvBgDsm7iqBp90dQhoYSuRHLm0I/nrwA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - tslib "^1.9.3" - -"@material/linear-progress@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-5.0.0-canary.50f110a6c.0.tgz#f7e2e6c8d314638f2384fc99fe3e4a28b4ef26db" - integrity sha512-PMZQdSY6oQ4PMSXgpMNLTB+I627yz3QfndO+dYY9qfxBAzyTRJe4+ZQ8BirhOL57N36SaWie+gEWHsHvh7Jn5Q== +"@material/linear-progress@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-5.0.0-canary.80a4d326f.0.tgz#18edb8c507f42842a8b6f514168b93f60c473308" + integrity sha512-OcqCiq4fxdy58aVkaCfFviQdA/gp32CMslKlPiuBjVxkT3J89FVD/TMrCdXEXlKq27FV7o91t2iKXH5XloJ5aA== dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/list@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/list/-/list-5.0.0-canary.50f110a6c.0.tgz#5ee428de8cc7cdd981f0fe88b919a4a07bcc860c" - integrity sha512-XuITdf0D4cSbeCIBQOFwIndsDCGC/GjE1cLN9f4bbOTrGJt6zAaO9p3GOd8msRE54NOc5e7yjNDMF76vdV/OuA== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/list@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/list/-/list-5.0.0-canary.80a4d326f.0.tgz#3deb8b34eed57d9a4fb307c82c5a7a5d5e05437c" + integrity sha512-DeyH+gc2gclYEWMRki+yeWFvSRc0fWX3GKCmha7DNzkflAXuMN+9vhAzs5IC0NT9ZIH28RRt6eMI46ByCNqc/A== + dependencies: + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/menu-surface@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-5.0.0-canary.50f110a6c.0.tgz#c797407ecd74ef1aa96ebcdd9b0895f242f376e3" - integrity sha512-6OLRaqrwzT9LKkh3sgAUC+4toDkklC9IL31fXLks0Ujp8hJ+ZhyY0Yw6yKj9JP2RjlRSAE5pmGtT8n5ujuA1hA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" +"@material/menu-surface@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-5.0.0-canary.80a4d326f.0.tgz#6f676615e5fee910c1ac1ae8c5bc43e3782c5791" + integrity sha512-QzfvfBTV5SzbMV6pFcSC/x7GAjE6caRX2jy9prBxFY9Jfr3GqDNWyL0ZrxXz7Llxv/WqyYrPBF9R0J+owcNZ8Q== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/menu@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/menu/-/menu-5.0.0-canary.50f110a6c.0.tgz#633bcc816d17aece3511ae25db8a1780ffa9e6ab" - integrity sha512-o55NHbnQCKLb0QKhQdOkNIRPYOASHPtYwmNiFnASB02sN2Rhep7BqRLlDY69iVMgs1zaMIEll/ntN1MNFy8EYw== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/list" "5.0.0-canary.50f110a6c.0" - "@material/menu-surface" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" +"@material/menu@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/menu/-/menu-5.0.0-canary.80a4d326f.0.tgz#677b063f937eb4ee8bd82a23ba8787a519b05eb9" + integrity sha512-y2oQUJ+dysZzh4Fv585QSxB7uTD9VSmoUIhKwcDcBiYLnYdG7+58ZdrSvotGmZjdnoezwJnPZbq1Y7/kZNvG6w== + dependencies: + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/list" "5.0.0-canary.80a4d326f.0" + "@material/menu-surface" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/notched-outline@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-5.0.0-canary.50f110a6c.0.tgz#afdb7fcac6819ec083a5cdb0ca26c9b48e2d6b40" - integrity sha512-Ml29ULqU7ZbFecZT4Nc5X1tp0zIR4zITXsUyGZqYnKEVXCILz6mLJ2SOz/uCnt4oNf7c9ygqGd82uiLpcTjiPw== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/floating-label" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" +"@material/notched-outline@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-5.0.0-canary.80a4d326f.0.tgz#80dd4a06ed4182231a7a2411263deb1d6f7e783c" + integrity sha512-i6YdzNLmsvQjwxFBcGY1/DrQ30PvvtWMh0qbPBhL0WOtaTZ9uhQnh/KS/ZNyVTnQlt5LzDwARsCGJE1DFyJr+w== + dependencies: + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/floating-label" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/radio@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/radio/-/radio-5.0.0-canary.50f110a6c.0.tgz#6188b17236ab4095479f16d8e5868d095d6ed06a" - integrity sha512-U4XYC6FDT4LNfZv30mjwli4MTKGAQ7myB4kWlrQcNG7BhYRr0FX532+5YS+sfmjhxgnH7idk+/leQ2m8szgazQ== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" +"@material/radio@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/radio/-/radio-5.0.0-canary.80a4d326f.0.tgz#8a31d0f14158ccd3730a3445f594d318d4abb10d" + integrity sha512-JtbeCPjDWjLMwSalBknOVBRE4A1g0oqdFHs5IEtR5JO+jTvQ3VhHPlQgIxqxhvwKW2AgcwQIe1sMZ+jmyQe/Yg== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/ripple@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-5.0.0-canary.50f110a6c.0.tgz#bb9f9a13fb09e33de7d192f85bc30052d7086ab1" - integrity sha512-m5ZOV2h4bVnmQenLCTpptFTSaojjJ9TWbmLowhl6KYpnXOKeVtuRyli35BpF3p/9ShBrJJhjjZbHzaVuyWRVVA== +"@material/ripple@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-5.0.0-canary.80a4d326f.0.tgz#fdf30d2b67326398dcf2a2c0c325f2a5bc750a08" + integrity sha512-b9XGZ+nr1cHnvOICP53jWfsYMTYBpjou9WVTJoNGWFNcZmAQmjdyPqxhT6f6+djg+IBF4dV6jWXZMTVPd0mhqg== dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/rtl@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-5.0.0-canary.50f110a6c.0.tgz#a380cf45c57f7efc3b9bd3fa13677b082a7b786e" - integrity sha512-NrZJS53lFf9K9Mus337ExHIfRbLCEIkgyAavYcKeHjLncsyoGCcAFgl1U76MJbT8HqpT2aQBpvF58fPhG19g+Q== - -"@material/select@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/select/-/select-5.0.0-canary.50f110a6c.0.tgz#a8ec1952b3a0f13f3fa4c3b88631816e37aa3120" - integrity sha512-TpjXgNdKpcvAkMNR26dZoaUHSDd6NSsynl5AOxzNvLVhngjhEiSDLiRuYepz/UumzliiRnUlN6Vc4Wd+Lkb1FA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/floating-label" "5.0.0-canary.50f110a6c.0" - "@material/line-ripple" "5.0.0-canary.50f110a6c.0" - "@material/menu" "5.0.0-canary.50f110a6c.0" - "@material/menu-surface" "5.0.0-canary.50f110a6c.0" - "@material/notched-outline" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/rtl@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-5.0.0-canary.80a4d326f.0.tgz#db8f378f163b1be90e5a154586405a169a346867" + integrity sha512-4Cf8A5hMVNFocxjXhrEaPcANbRL2ipHOJvQlWv1Aa6RBDY5UwO1v5LxJ+L822R3Kr4jx0YAu91fmtZYqz2/zZQ== + +"@material/select@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/select/-/select-5.0.0-canary.80a4d326f.0.tgz#4b867e6fdb2110ac0f9387539d87aa983f088b37" + integrity sha512-FddLyk5noaGXDdDK3/k6+MmOb1Qk9t9VMm2eDOA7Fwto2HlR5gX19R4T/kG897/q9TIxQ4fq8HmhsGCJ3Y077g== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/floating-label" "5.0.0-canary.80a4d326f.0" + "@material/line-ripple" "5.0.0-canary.80a4d326f.0" + "@material/menu" "5.0.0-canary.80a4d326f.0" + "@material/menu-surface" "5.0.0-canary.80a4d326f.0" + "@material/notched-outline" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/shape@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/shape/-/shape-5.0.0-canary.50f110a6c.0.tgz#1003d117c505f0a1ca4cd704bf908425c8265f34" - integrity sha512-AW3KdPg1fi0vD5iLtj8WcmQUuZ7khyiUS7KF2pkdP80OfAbbTLNQkyeFBmMmufe+vOxknn6gZqxFSMpg4AE27w== - dependencies: - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - -"@material/slider@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/slider/-/slider-5.0.0-canary.50f110a6c.0.tgz#4a497109a40b435592a6dbb84aad87c282c0e9bc" - integrity sha512-z1DL7TDInaEb3CylGGGKncEtjNloCqFgxaH8v2MKul87soqRVIu1QR/zPbjHV7j/QQ3f0Wm0EYKcBd+mQw6A7A== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/shape@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/shape/-/shape-5.0.0-canary.80a4d326f.0.tgz#192f43f62bf82d954035276669e1911908300149" + integrity sha512-cT1MOXaKUDgBfXs6MxBSHGK6r7b7LU/uyubUg24o+yM0KvR8XcHjlzm6dW1cRTr6bmfyFsCBEOyEQOFDTzRKVw== + dependencies: + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + +"@material/slider@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/slider/-/slider-5.0.0-canary.80a4d326f.0.tgz#3457af3c8828d38b6d1cd41a18586aacb6cfc5bf" + integrity sha512-G/3qCMPrNEZ/Q9uNczu7M+WJco+7lBKwYAvOeMsXQHrHZLqOF/iXzG/Eq8A29M1k7DCFwgdL2P+FJ/o8PJDPAg== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/snackbar@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-5.0.0-canary.50f110a6c.0.tgz#05b1923474892229e04c8ddd4314def5f8469f3f" - integrity sha512-IartC6DaaaY73G8oDYsboXsuxLdhQz0xV0LTTm4B23eLELjy/Fhtjcmbj/cmc5aGbRZh708JqwTw8wfX2Zx/pw== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/button" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/icon-button" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/snackbar@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-5.0.0-canary.80a4d326f.0.tgz#f8ab651f3baa042a7c6653ca1d791fed6341cdf2" + integrity sha512-kcxE5Pu/QpmJdWfANUPpkIvXieYd1XFkvoZrHBjERcSsVgadp4vswHkL6ZOgAY/UOtW4d31Vx6l/OJimqC1JOQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/button" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/icon-button" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/switch@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/switch/-/switch-5.0.0-canary.50f110a6c.0.tgz#091d0c9aa7c58b2db1584e376bc720ca5dad7388" - integrity sha512-JV9DrKkkPNr42Q/wZKWdy8vgYcvV9MwvoXuLF5W7QYhIO7AeZ10bzL7E/x3bsi2FZABg67WIU0YCUDx4J3hWiQ== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" +"@material/switch@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/switch/-/switch-5.0.0-canary.80a4d326f.0.tgz#21de413f1d7cc581bb3bc178ad14fcad2b5f390c" + integrity sha512-b4VXqw/8Lyd7pcQTtAsXTW3CZpTNtCVbwIrO6t1/QvreR7XoH3yQvNrsDPN8PJFPUu/TDmPirqDrttMr2lr7rQ== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/tab-bar@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-5.0.0-canary.50f110a6c.0.tgz#6683b3589f1591d47a740198895af74a1d86049c" - integrity sha512-0d51xjceKRuPkg+q8gPMM8RXqr0HJNChNypcdhYlz4We5X4P054aTPYfFuGEZLZLh6OkYXuO1CGQvtFRzT165Q== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/tab" "5.0.0-canary.50f110a6c.0" - "@material/tab-scroller" "5.0.0-canary.50f110a6c.0" +"@material/tab-bar@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-5.0.0-canary.80a4d326f.0.tgz#239a9ae9c66ec2d088968e665b6c2b456558c24f" + integrity sha512-1CMKdikXei9toKtkSRdcxV/8ZT5TJ7Ml0+xhAQmQOUUEKS3nqOX7j2KTuuQAyYndlH2UNEXoF33jmHtYNOOvUw== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/tab" "5.0.0-canary.80a4d326f.0" + "@material/tab-scroller" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/tab-indicator@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-5.0.0-canary.50f110a6c.0.tgz#90b5c8c718ab5084ae7da6dd591e91b86e74950a" - integrity sha512-A6OOd71ZWcUb0WfV/3HTQ0FfFgdzjkoaHIeY5WFnr0BuT0flS3kIaLf9G8IljkFAwaSoDLgUuWSeU9pPDgUJrw== +"@material/tab-indicator@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-5.0.0-canary.80a4d326f.0.tgz#db5dea32e0c6060535bc1179cd4d7733004e6b5b" + integrity sha512-2lD0N+AMlpNUtgCpHilW4960dLu/k25i1WQVukZ5uns14mpGaqAiGb2BRTu/D7HN4fa+GzvxsF4E+JEn9m2mFw== dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/tab-scroller@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-5.0.0-canary.50f110a6c.0.tgz#4ceea6a4202120e9c43dfbcc7f04cc8197899747" - integrity sha512-79P5xPVcufboGQF4suq9vxXhHLs4sPyJpIz6B7nCLCwshNL0Ab5kQAyKo6prfxpudVaj5LNx52F3nnp8ly88+A== +"@material/tab-scroller@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-5.0.0-canary.80a4d326f.0.tgz#72ea0d90e3c50527ba5734741ded6df9c6f4506e" + integrity sha512-wRuRwScSnUBeV8osZTZ/IT47M6HQw9Rp20gd171IPA4MhYiArfKN/bLuim6t0k2IO2SVKI61uQZ8NAtE8TBcDQ== dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/tab" "5.0.0-canary.50f110a6c.0" + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/tab" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/tab@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/tab/-/tab-5.0.0-canary.50f110a6c.0.tgz#3f10ee6023370be90c7fdb57910106d7226292ad" - integrity sha512-Oda9hJebcFweb3d9RWphvuOgX9iB0yDDZFmqF+xXW5sr8xJ7faw7MjoRXOIpFd8zgVtqzK7LDNXMqJ7EDc6JVA== - dependencies: - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/tab-indicator" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/tab@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/tab/-/tab-5.0.0-canary.80a4d326f.0.tgz#b7351c8d8f8d61d4c2d5c60e6160ebf62fe02c13" + integrity sha512-Hx/qqnfXCmvc17VoGBdmp/ybbY40pcDlIqQzuw3lq504xKMITAg10H7fr2X/AKOld9ou8BuUXgnJGVeCrJoSIg== + dependencies: + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/tab-indicator" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/textfield@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-5.0.0-canary.50f110a6c.0.tgz#e1052ffb2aa1a498a105d80e6474918c370ec2fc" - integrity sha512-ziEexannx1z0KBOBoEewrHvTvlQ2KXZHHzNqfaoaN8FqC2UWor4ziL3X59jvJxy5WWxLLcajGLQzTFjq3Wi3ow== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/floating-label" "5.0.0-canary.50f110a6c.0" - "@material/line-ripple" "5.0.0-canary.50f110a6c.0" - "@material/notched-outline" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/textfield@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-5.0.0-canary.80a4d326f.0.tgz#a4cdf8053112c4af92415eb7105527f6a0e1c2d9" + integrity sha512-hGqLnTXQSFy5NzNqjwSzju2p1sUQ3SU2WCdA1soGNF/1wJiMOadk2iUv0AUFT5yflV8uS1zuhhU/VD/YztGlLw== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/floating-label" "5.0.0-canary.80a4d326f.0" + "@material/line-ripple" "5.0.0-canary.80a4d326f.0" + "@material/notched-outline" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/theme@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/theme/-/theme-5.0.0-canary.50f110a6c.0.tgz#22a9439216a2a58ddfef9673272a788037471a2e" - integrity sha512-TgJujafdFIX9M6J/iwdzkGjJXNnj+UxupzDrlJKIvWC6nU3PBmQu7A566C4cFqatp/OHHsYKB0u5QqpepUbqxA== - dependencies: - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - -"@material/top-app-bar@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-5.0.0-canary.50f110a6c.0.tgz#30010d7576b7d734c3a18deef8c48cebad326d67" - integrity sha512-UkwrdvjxEj1f95p8st9xsvfNfInssQopirEB6rUGpnTCHOIgczGnFkJpBW7BHR5mJHqYFhBYUsABAkX+79OZmA== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/icon-button" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" +"@material/theme@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/theme/-/theme-5.0.0-canary.80a4d326f.0.tgz#56aaffa6adf5a8e5074cbeb51dc654cd86ca0dd9" + integrity sha512-sfcurCSfZlHW7rI2UELFF3x1JB2iYOROMpUd+553SF6MIJrg+6Xg2NcE0egFvbefKeMKFIwP0+dMZRAEv6awKg== + dependencies: + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + +"@material/top-app-bar@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-5.0.0-canary.80a4d326f.0.tgz#e952a65971cec69cb63a9ed37bfecb3019d7ce66" + integrity sha512-yTnNE4aK4qhzcgKRxvsDXfhgGuuZaJYxnkAN3vOxUEl9vvX1XG8LwU8sItixsF07wDT15JXg0aY7667oREWQFw== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" tslib "^1.9.3" -"@material/touch-target@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-5.0.0-canary.50f110a6c.0.tgz#b0a114291d344251d6eaf726feb980f3ca35d76e" - integrity sha512-SCTukshb6iHgXDS7gjBHvt5u0gyjbzETbGSXKubsKBWbf9l7ERlUq2/u6MMrNlBztFC9Bq0xiwCw3T1pK0wUrw== +"@material/touch-target@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-5.0.0-canary.80a4d326f.0.tgz#61710ce0f203c0bfb427c1a015f6162592721868" + integrity sha512-FuHQHyOoXI7XZ/dseiznDKB+fhYNaxNTo+t7+wjaVmR/tP0kbvsvACtfKVLjVMJdriWEwSm73qiZeFhF1b3PQA== dependencies: - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" -"@material/typography@5.0.0-canary.50f110a6c.0": - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/@material/typography/-/typography-5.0.0-canary.50f110a6c.0.tgz#03a79af49cbdfc83f182c7a348d5c2b104fc0f29" - integrity sha512-VzA3eFpX1RD8w3jSPiZ15K+GXqnu7gbIv95n6Y8wJV1ykBV4aQ/1ElpPUpbfvWXlScfP0u3PLyLYkCSB+v90DA== +"@material/typography@5.0.0-canary.80a4d326f.0": + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/@material/typography/-/typography-5.0.0-canary.80a4d326f.0.tgz#ceeb47e5b3358f754ce18ffd506030b5a9e26437" + integrity sha512-ntExE1kVDRQzmziHGIrRD74Sf37aCM/CifnctO1ZdNaYCUhKITQq7DlKHN8sfPW35OZ9bAxEeP+2LOU+/VE6jQ== dependencies: - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" "@microsoft/api-extractor-model@7.4.1": version "7.4.1" @@ -1019,13 +1024,13 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@schematics/angular@^9.0.0-rc.6": - version "9.0.0-rc.6" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-9.0.0-rc.6.tgz#52e414d1d9227edaed718dca70a7e48b7b85b626" - integrity sha512-Xc78VjRuvxCzQOw4WkpcSu/yOqxXwGHv+StrhZC5/aAGG2IPt5iTBLNwHrosGtv5agR9NrWtMFsqVQNQgJo5Xw== +"@schematics/angular@^9.0.0-rc.11": + version "9.0.0-rc.11" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-9.0.0-rc.11.tgz#d544c0d4e7b3dd59ed56be5183e038ebe06a165e" + integrity sha512-9InC+F71KiPXE0jl7Ow4iPFJ2AZZDbfTM6yWZoYLk3hzTCohAZZciBl00Tfyu2uerGshx8akbJMLySjXtf+q0g== dependencies: - "@angular-devkit/core" "9.0.0-rc.6" - "@angular-devkit/schematics" "9.0.0-rc.6" + "@angular-devkit/core" "9.0.0-rc.11" + "@angular-devkit/schematics" "9.0.0-rc.11" "@types/archy@^0.0.31": version "0.0.31" @@ -1055,6 +1060,11 @@ "@types/events" "*" "@types/node" "*" +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" @@ -1250,6 +1260,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-glob/-/parse-glob-3.0.29.tgz#6a40ec7ebd2418ee69ee397e48e42169268a10bf" integrity sha1-akDsfr0kGO5p7jl+SOQhaSaKEL8= +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "@types/parse5@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.0.tgz#9ae2106efc443d7c1e26570aa8247828c9c80f11" @@ -1570,7 +1585,7 @@ agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" -ajv@6.10.2: +ajv@6.10.2, ajv@^6.10.2: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== @@ -1590,16 +1605,6 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.9.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -1661,6 +1666,11 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" @@ -1678,6 +1688,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.0.tgz#5681f0dcf7ae5880a7841d8831c4724ed9cc0172" + integrity sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -1984,18 +2002,18 @@ autoprefixer@^6.7.6: postcss "^5.2.16" postcss-value-parser "^3.2.3" -autoprefixer@^9.5.1: - version "9.6.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87" - integrity sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ== +autoprefixer@^9.7.1: + version "9.7.3" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.3.tgz#fd42ed03f53de9beb4ca0d61fb4f7268a9bb50b4" + integrity sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q== dependencies: - browserslist "^4.6.1" - caniuse-lite "^1.0.30000971" + browserslist "^4.8.0" + caniuse-lite "^1.0.30001012" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.16" - postcss-value-parser "^3.3.1" + postcss "^7.0.23" + postcss-value-parser "^4.0.2" aws-sign2@~0.7.0: version "0.7.0" @@ -2334,14 +2352,14 @@ browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^4.6.1: - version "4.6.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.3.tgz#0530cbc6ab0c1f3fc8c819c72377ba55cf647f05" - integrity sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ== +browserslist@^4.8.0: + version "4.8.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289" + integrity sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA== dependencies: - caniuse-lite "^1.0.30000975" - electron-to-chromium "^1.3.164" - node-releases "^1.1.23" + caniuse-lite "^1.0.30001015" + electron-to-chromium "^1.3.322" + node-releases "^1.1.42" browserstack@1.5.0: version "1.5.0" @@ -2495,6 +2513,11 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camel-case@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" @@ -2545,10 +2568,10 @@ caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000914.tgz#5df1edfb2407dd857bf33e7f346514316c7b9fa3" integrity sha512-UbjlrZOQowyqrPwFKFPZ4M7LngssN5FyWpvzuFKYiQoZD8J+bPYU4s0rSiKPTzFzDYNEP9w5E5+MQj3+TqW+gA== -caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30000975: - version "1.0.30000976" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000976.tgz#d30fe12662cb2a21e130d307db9907513ca830a2" - integrity sha512-tleNB1IwPRqZiod6nUNum63xQCMN96BUO2JTeiwuRM7p9d616EHsMBjBWJMudX39qCaPuWY8KEWzMZq7A9XQMQ== +caniuse-lite@^1.0.30001012, caniuse-lite@^1.0.30001015: + version "1.0.30001016" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz#16ea48d7d6e8caf3cad3295c2d746fe38c4e7f66" + integrity sha512-yYQ2QfotceRiH4U+h1Us86WJXtVHDmy3nEKIdYPsZCYnOV5/tMgGbmoIlrMzmh2VXlproqYtVaKeGDBkMZifFA== canonical-path@1.0.0, canonical-path@^1.0.0: version "1.0.0" @@ -2637,6 +2660,14 @@ chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + change-case@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.0.tgz#6c9c8e35f8790870a82b6b0745be8c3cbef9b081" @@ -2745,25 +2776,6 @@ chokidar@^2.0.4, chokidar@^2.1.2: optionalDependencies: fsevents "^1.2.7" -chokidar@^2.1.1: - version "2.1.5" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" - integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - chokidar@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" @@ -2963,11 +2975,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" @@ -3373,15 +3397,16 @@ cosmiconfig@^5.0.7: js-yaml "^3.9.0" parse-json "^4.0.0" -cosmiconfig@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" crc32-stream@^3.0.1: version "3.0.1" @@ -4036,10 +4061,10 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz#f36ab32634f49ef2b0fdc1e82e2d1cc17feb29e7" integrity sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A== -electron-to-chromium@^1.3.164: - version "1.3.170" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.170.tgz#2dc858f8a9bb51bbfe3a429312c11f565b456e61" - integrity sha512-vDEhHcwMogbM+WXDTh6ZktwQOqLcK7MJdCOM99UZXRI1ct3Y9OeYYJTrIHnswzv+IYwoXNj0Furh+K6UotcHVg== +electron-to-chromium@^1.3.322: + version "1.3.322" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" + integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA== emoji-regex@^7.0.1: version "7.0.3" @@ -4779,14 +4804,6 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= -focus-trap@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-5.0.1.tgz#285f9df2cd9f5ef82dd1abb5d8a70e66cd4f99e3" - integrity sha512-vU7zEdL3y+kfkuwBbT9456JH8QfyemdcdZ2gKMfmgLyAs9NQAkSVQBSZmb9nlb1cVMo+iCsddqeGJog00pd2EQ== - dependencies: - tabbable "^4.0.0" - xtend "^4.0.1" - follow-redirects@1.5.10, follow-redirects@^1.0.0: version "1.5.10" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" @@ -5313,7 +5330,7 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -gonzales-pe@^4.2.3: +gonzales-pe@^4.2.3, gonzales-pe@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.4.tgz#356ae36a312c46fe0f1026dd6cb539039f8500d2" integrity sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ== @@ -5573,6 +5590,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-gulplog@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" @@ -5646,10 +5668,10 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== -html-tags@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.0.0.tgz#41f57708c9e6b7b46a00a22317d614c4a2bab166" - integrity sha512-xiXEBjihaNI+VZ2mKEoI5ZPxqUsevTKM+aeeJ/W4KAg2deGE35minmCJMn51BvwJZmiHaeAxrb2LAS0yZJxuuA== +html-tags@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" + integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== htmlparser2@^3.10.0: version "3.10.1" @@ -5797,10 +5819,10 @@ ignore@^4.0.3: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.6: - version "5.1.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558" - integrity sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ== +ignore@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" + integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== immediate@~3.0.5: version "3.0.6" @@ -5820,6 +5842,14 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" +import-fresh@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -6303,7 +6333,7 @@ is-text-path@^1.0.0: dependencies: text-extensions "^1.0.0" -is-typedarray@~1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -6788,10 +6818,10 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -known-css-properties@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.14.0.tgz#d7032b4334a32dc22e6e46b081ec789daf18756c" - integrity sha512-P+0a/gBzLgVlCnK8I7VcD0yuYJscmWn66wH9tlKsQnmVdg689tLEmziwB9PuazZYLkcm07fvWOKCJJqI55sD5Q== +known-css-properties@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.17.0.tgz#1c535f530ee8e9e3e27bb6a718285780e1d07326" + integrity sha512-Vi3nxDGMm/z+lAaCjvAR1u+7fiv+sG6gU/iYDj5QOF8h76ytK9EW/EKfF0NeTyiGBi8Jy6Hklty/vxISrLox3w== latest-version@^3.0.0: version "3.1.0" @@ -6860,6 +6890,11 @@ limiter@^1.0.5: resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.4.tgz#87c9c3972d389fdb0ba67a45aadbc5d2f8413bc1" integrity sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg== +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + listenercount@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" @@ -7254,7 +7289,7 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@^4.17.14, lodash@~4.17.15: +lodash@^4.17.14, lodash@^4.17.15, lodash@~4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -7494,57 +7529,56 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" -material-components-web@5.0.0-canary.50f110a6c.0: - version "5.0.0-canary.50f110a6c.0" - resolved "https://registry.yarnpkg.com/material-components-web/-/material-components-web-5.0.0-canary.50f110a6c.0.tgz#1aa7f4d0493379a6adbe81070a22ce89e4ef031a" - integrity sha512-SHeTQz2Kvf0T2QUPmyigR3ze20fXyZtvcnb/grQHrMWdm3FczOhR0yPb8xh1AeLJ9SF6V6+fB1Mgdxw9fW70xQ== - dependencies: - "@material/animation" "5.0.0-canary.50f110a6c.0" - "@material/auto-init" "5.0.0-canary.50f110a6c.0" - "@material/base" "5.0.0-canary.50f110a6c.0" - "@material/button" "5.0.0-canary.50f110a6c.0" - "@material/card" "5.0.0-canary.50f110a6c.0" - "@material/checkbox" "5.0.0-canary.50f110a6c.0" - "@material/chips" "5.0.0-canary.50f110a6c.0" - "@material/data-table" "5.0.0-canary.50f110a6c.0" - "@material/density" "5.0.0-canary.50f110a6c.0" - "@material/dialog" "5.0.0-canary.50f110a6c.0" - "@material/dom" "5.0.0-canary.50f110a6c.0" - "@material/drawer" "5.0.0-canary.50f110a6c.0" - "@material/elevation" "5.0.0-canary.50f110a6c.0" - "@material/fab" "5.0.0-canary.50f110a6c.0" - "@material/feature-targeting" "5.0.0-canary.50f110a6c.0" - "@material/floating-label" "5.0.0-canary.50f110a6c.0" - "@material/form-field" "5.0.0-canary.50f110a6c.0" - "@material/grid-list" "5.0.0-canary.50f110a6c.0" - "@material/icon-button" "5.0.0-canary.50f110a6c.0" - "@material/image-list" "5.0.0-canary.50f110a6c.0" - "@material/layout-grid" "5.0.0-canary.50f110a6c.0" - "@material/line-ripple" "5.0.0-canary.50f110a6c.0" - "@material/linear-progress" "5.0.0-canary.50f110a6c.0" - "@material/list" "5.0.0-canary.50f110a6c.0" - "@material/menu" "5.0.0-canary.50f110a6c.0" - "@material/menu-surface" "5.0.0-canary.50f110a6c.0" - "@material/notched-outline" "5.0.0-canary.50f110a6c.0" - "@material/radio" "5.0.0-canary.50f110a6c.0" - "@material/ripple" "5.0.0-canary.50f110a6c.0" - "@material/rtl" "5.0.0-canary.50f110a6c.0" - "@material/select" "5.0.0-canary.50f110a6c.0" - "@material/shape" "5.0.0-canary.50f110a6c.0" - "@material/slider" "5.0.0-canary.50f110a6c.0" - "@material/snackbar" "5.0.0-canary.50f110a6c.0" - "@material/switch" "5.0.0-canary.50f110a6c.0" - "@material/tab" "5.0.0-canary.50f110a6c.0" - "@material/tab-bar" "5.0.0-canary.50f110a6c.0" - "@material/tab-indicator" "5.0.0-canary.50f110a6c.0" - "@material/tab-scroller" "5.0.0-canary.50f110a6c.0" - "@material/textfield" "5.0.0-canary.50f110a6c.0" - "@material/theme" "5.0.0-canary.50f110a6c.0" - "@material/top-app-bar" "5.0.0-canary.50f110a6c.0" - "@material/touch-target" "5.0.0-canary.50f110a6c.0" - "@material/typography" "5.0.0-canary.50f110a6c.0" - -mathml-tag-names@^2.1.0: +material-components-web@5.0.0-canary.80a4d326f.0: + version "5.0.0-canary.80a4d326f.0" + resolved "https://registry.yarnpkg.com/material-components-web/-/material-components-web-5.0.0-canary.80a4d326f.0.tgz#7dedb6b5a3f21e5b3fc8fc2c9976bb837ecf75fd" + integrity sha512-zQOFQvvVJ7qLmNDqEhuq7tzkiYSiTw2O308EprRC1fkEjeuNiU4TY3W3xn1L4MFs5kYrtuqf+EiZYJJe2GGEDg== + dependencies: + "@material/animation" "5.0.0-canary.80a4d326f.0" + "@material/auto-init" "5.0.0-canary.80a4d326f.0" + "@material/base" "5.0.0-canary.80a4d326f.0" + "@material/button" "5.0.0-canary.80a4d326f.0" + "@material/card" "5.0.0-canary.80a4d326f.0" + "@material/checkbox" "5.0.0-canary.80a4d326f.0" + "@material/chips" "5.0.0-canary.80a4d326f.0" + "@material/data-table" "5.0.0-canary.80a4d326f.0" + "@material/density" "5.0.0-canary.80a4d326f.0" + "@material/dialog" "5.0.0-canary.80a4d326f.0" + "@material/dom" "5.0.0-canary.80a4d326f.0" + "@material/drawer" "5.0.0-canary.80a4d326f.0" + "@material/elevation" "5.0.0-canary.80a4d326f.0" + "@material/fab" "5.0.0-canary.80a4d326f.0" + "@material/feature-targeting" "5.0.0-canary.80a4d326f.0" + "@material/floating-label" "5.0.0-canary.80a4d326f.0" + "@material/form-field" "5.0.0-canary.80a4d326f.0" + "@material/icon-button" "5.0.0-canary.80a4d326f.0" + "@material/image-list" "5.0.0-canary.80a4d326f.0" + "@material/layout-grid" "5.0.0-canary.80a4d326f.0" + "@material/line-ripple" "5.0.0-canary.80a4d326f.0" + "@material/linear-progress" "5.0.0-canary.80a4d326f.0" + "@material/list" "5.0.0-canary.80a4d326f.0" + "@material/menu" "5.0.0-canary.80a4d326f.0" + "@material/menu-surface" "5.0.0-canary.80a4d326f.0" + "@material/notched-outline" "5.0.0-canary.80a4d326f.0" + "@material/radio" "5.0.0-canary.80a4d326f.0" + "@material/ripple" "5.0.0-canary.80a4d326f.0" + "@material/rtl" "5.0.0-canary.80a4d326f.0" + "@material/select" "5.0.0-canary.80a4d326f.0" + "@material/shape" "5.0.0-canary.80a4d326f.0" + "@material/slider" "5.0.0-canary.80a4d326f.0" + "@material/snackbar" "5.0.0-canary.80a4d326f.0" + "@material/switch" "5.0.0-canary.80a4d326f.0" + "@material/tab" "5.0.0-canary.80a4d326f.0" + "@material/tab-bar" "5.0.0-canary.80a4d326f.0" + "@material/tab-indicator" "5.0.0-canary.80a4d326f.0" + "@material/tab-scroller" "5.0.0-canary.80a4d326f.0" + "@material/textfield" "5.0.0-canary.80a4d326f.0" + "@material/theme" "5.0.0-canary.80a4d326f.0" + "@material/top-app-bar" "5.0.0-canary.80a4d326f.0" + "@material/touch-target" "5.0.0-canary.80a4d326f.0" + "@material/typography" "5.0.0-canary.80a4d326f.0" + +mathml-tag-names@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw== @@ -7679,7 +7713,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0: +micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -8035,14 +8069,14 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.23: - version "1.1.23" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.23.tgz#de7409f72de044a2fa59c097f436ba89c39997f0" - integrity sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w== +node-releases@^1.1.42: + version "1.1.43" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.43.tgz#2c6ca237f88ce11d49631f11190bb01f8d0549f2" + integrity sha512-Rmfnj52WNhvr83MvuAWHEqXVoZXCcDQssSOffU4n4XOL9sPrP61mSZ88g25NqmABDvH7PiAlFCzoSCSdzA293w== dependencies: - semver "^5.3.0" + semver "^6.3.0" -node-sass@^4.12.0, node-sass@^4.8.3: +node-sass@^4.8.3: version "4.12.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== @@ -8511,6 +8545,13 @@ param-case@^2.1.0: dependencies: no-case "^2.2.0" +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-entities@^1.0.2, parse-entities@^1.1.0: version "1.2.2" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" @@ -8552,6 +8593,16 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-json@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" + integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + lines-and-columns "^1.1.6" + parse-ms@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" @@ -8693,6 +8744,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -8807,10 +8863,10 @@ postcss-html@^0.36.0: dependencies: htmlparser2 "^3.10.0" -postcss-jsx@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.1.tgz#ab5e469e7449b84bd1a5973ff555fbe84c39f91d" - integrity sha512-xaZpy01YR7ijsFUtu5rViYCFHurFIPHir+faiOQp8g/NfTfWqZCKDhKrydQZ4d8WlSAmVdXGwLjpFbsNUI26Sw== +postcss-jsx@^0.36.3: + version "0.36.3" + resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.3.tgz#c91113eae2935a1c94f00353b788ece9acae3f46" + integrity sha512-yV8Ndo6KzU8eho5mCn7LoLUGPkXrRXRjhMpX4AaYJ9wLJPv099xbtpbRQ8FrPnzVxb/cuMebbPR7LweSt+hTfA== dependencies: "@babel/core" ">=7.2.2" @@ -8856,13 +8912,13 @@ postcss-safe-parser@^4.0.1: dependencies: postcss "^7.0.0" -postcss-sass@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.3.5.tgz#6d3e39f101a53d2efa091f953493116d32beb68c" - integrity sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A== +postcss-sass@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.2.tgz#7d1f8ddf6960d329de28fb3ff43c9c42013646bc" + integrity sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ== dependencies: - gonzales-pe "^4.2.3" - postcss "^7.0.1" + gonzales-pe "^4.2.4" + postcss "^7.0.21" postcss-scss@^2.0.0: version "2.0.0" @@ -8885,11 +8941,16 @@ postcss-syntax@^0.36.2: resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== -postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.1: +postcss-value-parser@^3.2.3: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss-value-parser@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" + integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== + postcss-values-parser@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz#5d9fa63e2bcb0179ce48f3235303765eb89f3047" @@ -8909,7 +8970,7 @@ postcss@^5.2.16: source-map "^0.5.6" supports-color "^3.2.3" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.2, postcss@^7.0.7: +postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.7: version "7.0.17" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== @@ -8918,6 +8979,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^7.0.21, postcss@^7.0.23: + version "7.0.25" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.25.tgz#dd2a2a753d50b13bed7a2009b4a18ac14d9db21e" + integrity sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postinstall-build@^5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/postinstall-build/-/postinstall-build-5.0.3.tgz#238692f712a481d8f5bc8960e94786036241efc7" @@ -9332,6 +9402,11 @@ regenerate@^1.2.1: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -9545,6 +9620,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -9802,6 +9882,13 @@ sass@^1.22.9: dependencies: chokidar ">=2.0.0 <4.0.0" +sass@^1.24.4: + version "1.24.4" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.24.4.tgz#aa50575a9ed2b9e9645b5599156fd149bdad9eaa" + integrity sha512-SqizkIEEcVPzmK1tYdlNRl/RSXMEwGcifL9GD+S2p9rEPdj6ycRbk4PWZs0jwlajNSyBPo/SXRB81i22SG0jmw== + dependencies: + chokidar ">=2.0.0 <4.0.0" + sauce-connect-launcher@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.2.4.tgz#8d38f85242a9fbede1b2303b559f7e20c5609a1c" @@ -10526,14 +10613,14 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.0.0" -string-width@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" - integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ== +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^5.2.0" + strip-ansi "^6.0.0" string_decoder@^1.1.1: version "1.2.0" @@ -10613,6 +10700,13 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" @@ -10660,15 +10754,15 @@ style-search@^0.1.0: resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= -stylelint@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-10.1.0.tgz#1bc4c4ce878107e7c396b19226d91ba28268911a" - integrity sha512-OmlUXrgzEMLQYj1JPTpyZPR9G4bl0StidfHnGJEMpdiQ0JyTq0MPg1xkHk1/xVJ2rTPESyJCDWjG8Kbpoo7Kuw== +stylelint@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-12.0.0.tgz#2e8613675f7be11769ce474f45137fdf7751380a" + integrity sha512-TwqtATrFOT07SPlUGyHN7tVhWqxwitn5BlAvyBQy/ekA+Nwu4mLU9L1dvGQPNxHUBLowjvkSW18QzHHR6/FVVQ== dependencies: - autoprefixer "^9.5.1" + autoprefixer "^9.7.1" balanced-match "^1.0.0" - chalk "^2.4.2" - cosmiconfig "^5.2.0" + chalk "^3.0.0" + cosmiconfig "^6.0.0" debug "^4.1.1" execall "^2.0.0" file-entry-cache "^5.0.1" @@ -10676,43 +10770,43 @@ stylelint@^10.1.0: global-modules "^2.0.0" globby "^9.2.0" globjoin "^0.1.4" - html-tags "^3.0.0" - ignore "^5.0.6" + html-tags "^3.1.0" + ignore "^5.1.4" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.14.0" + known-css-properties "^0.17.0" leven "^3.1.0" - lodash "^4.17.11" + lodash "^4.17.15" log-symbols "^3.0.0" - mathml-tag-names "^2.1.0" + mathml-tag-names "^2.1.1" meow "^5.0.0" - micromatch "^4.0.0" + micromatch "^4.0.2" normalize-selector "^0.2.0" - pify "^4.0.1" - postcss "^7.0.14" + postcss "^7.0.21" postcss-html "^0.36.0" - postcss-jsx "^0.36.1" + postcss-jsx "^0.36.3" postcss-less "^3.1.4" postcss-markdown "^0.36.0" postcss-media-query-parser "^0.2.3" postcss-reporter "^6.0.1" postcss-resolve-nested-selector "^0.1.1" postcss-safe-parser "^4.0.1" - postcss-sass "^0.3.5" + postcss-sass "^0.4.2" postcss-scss "^2.0.0" postcss-selector-parser "^3.1.0" postcss-syntax "^0.36.2" - postcss-value-parser "^3.3.1" + postcss-value-parser "^4.0.2" resolve-from "^5.0.0" - signal-exit "^3.0.2" slash "^3.0.0" specificity "^0.4.1" - string-width "^4.1.0" - strip-ansi "^5.2.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" style-search "^0.1.0" sugarss "^2.0.0" svg-tags "^1.0.0" - table "^5.2.3" + table "^5.4.6" + v8-compile-cache "^2.1.0" + write-file-atomic "^3.0.1" stylus-lookup@^3.0.1: version "3.0.2" @@ -10796,6 +10890,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" @@ -10837,18 +10938,13 @@ systemjs@0.19.43: dependencies: when "^3.7.5" -tabbable@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" - integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== - -table@^5.2.3: - version "5.4.1" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.1.tgz#0691ae2ebe8259858efb63e550b6d5f9300171e8" - integrity sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w== +table@^5.4.6: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== dependencies: - ajv "^6.9.1" - lodash "^4.17.11" + ajv "^6.10.2" + lodash "^4.17.14" slice-ansi "^2.1.0" string-width "^3.0.0" @@ -11160,10 +11256,10 @@ try-require@^1.0.0: resolved "https://registry.yarnpkg.com/try-require/-/try-require-1.2.1.tgz#34489a2cac0c09c1cc10ed91ba011594d4333be2" integrity sha1-NEiaLKwMCcHMEO2RugEVlNQzO+I= -ts-api-guardian@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/ts-api-guardian/-/ts-api-guardian-0.4.6.tgz#ebd9a700b40de6ca4dbc5c468e322fe86476b6db" - integrity sha512-V+AVEe4HCi3j0mKrvSBNZN6zoY0eIw2r9vcWrqu4vhGlpASgPTSwuPHtxctx4qMBH5ieyqO6rf1LrQfsr8s5tw== +ts-api-guardian@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/ts-api-guardian/-/ts-api-guardian-0.5.0.tgz#1b63546dfd61581054802bdc8d5915ceebcc2c35" + integrity sha512-BjVlb23FUqWU+wmdHkLdaHcllU+v/Cca26sY40bCkM15BCF2yc17daOm+UXyvxQ9NndPM/40m+X+GLyDkrE9tA== dependencies: chalk "^2.3.1" diff "^3.5.0" @@ -11193,10 +11289,10 @@ tsconfig@^6.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" -tsickle@^0.37.1: - version "0.37.1" - resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.37.1.tgz#2f8a87c1b15766e866457bd06fb6c0e0d84eed09" - integrity sha512-0GwgOJEnsmRsrONXCvcbAWY0CvdqF3UugPVoupUpA8Ul0qCPTuqqq0ou/hLqtKZOyyulzCP6MYRjb9/J1g9bJg== +tsickle@0.38.0, tsickle@^0.38.0: + version "0.38.0" + resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.38.0.tgz#89f5952c9bb3ba0b36dc384975e23cf90e584822" + integrity sha512-k7kI6afBuLd2jIrj9JR8lKhEkp99sFVRKQbHeaHQkdvDaH5AvzwqA/qX+aNj28OfuAsWryOKAZoXm24l7JelEw== tslib@^1.10.0: version "1.10.0" @@ -11303,15 +11399,22 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.6.4, typescript@^3.2.2, typescript@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" - integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== +typescript@3.7.4, typescript@^3.2.2, typescript@~3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" + integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== typescript@^3.0.3, typescript@^3.4.5, typescript@~3.5.3: version "3.5.3" @@ -11601,6 +11704,11 @@ uuid@^3.0.0, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +v8-compile-cache@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + v8-coverage@1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/v8-coverage/-/v8-coverage-1.0.9.tgz#780889680c0fea0f587adf22e2b5f443b9434745" @@ -11911,6 +12019,16 @@ write-file-atomic@^2.0.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write-file-atomic@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.1.tgz#558328352e673b5bb192cf86500d60b230667d4b" + integrity sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + write@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" @@ -12016,6 +12134,13 @@ yallist@^3.0.0, yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yaml@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2" + integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw== + dependencies: + "@babel/runtime" "^7.6.3" + yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" From 1ba70b0d995d0533db42c52a3cbee05656d782a9 Mon Sep 17 00:00:00 2001 From: Jan Malchert <25508038+JanMalch@users.noreply.github.com> Date: Mon, 3 Feb 2020 20:45:41 +0100 Subject: [PATCH 10/10] fix: fix datepicker panelClass declaration in public_api_guard --- tools/public_api_guard/material/datepicker.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/public_api_guard/material/datepicker.d.ts b/tools/public_api_guard/material/datepicker.d.ts index 73ccfa9891a7..815f8928057d 100644 --- a/tools/public_api_guard/material/datepicker.d.ts +++ b/tools/public_api_guard/material/datepicker.d.ts @@ -129,7 +129,8 @@ export declare class MatDatepicker implements OnDestroy, CanColor { get opened(): boolean; set opened(value: boolean); openedStream: EventEmitter; - panelClass: string | string[]; + get panelClass(): string | string[]; + set panelClass(value: string | string[]); get startAt(): D | null; set startAt(value: D | null); startView: 'month' | 'year' | 'multi-year';