diff --git a/src/material-experimental/mdc-chips/chip-grid.spec.ts b/src/material-experimental/mdc-chips/chip-grid.spec.ts index 4a2dde5c3152..7dfc380dbb4d 100644 --- a/src/material-experimental/mdc-chips/chip-grid.spec.ts +++ b/src/material-experimental/mdc-chips/chip-grid.spec.ts @@ -239,6 +239,14 @@ describe('MDC-based MatChipGrid', () => { expect(chipGridInstance._keyManager.activeColumnIndex).toBe(0); })); }); + + it('should have a focus indicator', () => { + const focusableTextNativeElements = Array.from(chipGridNativeElement + .querySelectorAll('.mat-chip-row-focusable-text-content')); + + expect(focusableTextNativeElements + .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true); + }); }); describe('keyboard behavior', () => { diff --git a/src/material-experimental/mdc-chips/chip-icons.ts b/src/material-experimental/mdc-chips/chip-icons.ts index 10522c78aa23..10e90102caf0 100644 --- a/src/material-experimental/mdc-chips/chip-icons.ts +++ b/src/material-experimental/mdc-chips/chip-icons.ts @@ -105,8 +105,8 @@ const _MatChipRemoveMixinBase: selector: '[matChipRemove]', inputs: ['disabled', 'tabIndex'], host: { - 'class': - 'mat-mdc-chip-remove mat-mdc-chip-trailing-icon mdc-chip__icon mdc-chip__icon--trailing', + 'class': `mat-mdc-chip-remove mat-mdc-chip-trailing-icon mat-mdc-focus-indicator + mdc-chip__icon mdc-chip__icon--trailing`, '[tabIndex]': 'tabIndex', 'role': 'button', '(click)': 'interaction.next($event)', diff --git a/src/material-experimental/mdc-chips/chip-option.html b/src/material-experimental/mdc-chips/chip-option.html index c4feed6d2062..f9d467066596 100644 --- a/src/material-experimental/mdc-chips/chip-option.html +++ b/src/material-experimental/mdc-chips/chip-option.html @@ -1,4 +1,11 @@ + + +
diff --git a/src/material-experimental/mdc-chips/chip-option.spec.ts b/src/material-experimental/mdc-chips/chip-option.spec.ts index 5e900ff4391c..13978b5cc114 100644 --- a/src/material-experimental/mdc-chips/chip-option.spec.ts +++ b/src/material-experimental/mdc-chips/chip-option.spec.ts @@ -3,7 +3,6 @@ import {SPACE} from '@angular/cdk/keycodes'; import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing/private'; import {Component, DebugElement, ViewChild} from '@angular/core'; import {async, ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; -import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import { @@ -20,17 +19,14 @@ describe('MDC-based Option Chips', () => { let chipDebugElement: DebugElement; let chipNativeElement: HTMLElement; let chipInstance: MatChipOption; - let globalRippleOptions: RippleGlobalOptions; let dir = 'ltr'; beforeEach(async(() => { - globalRippleOptions = {}; TestBed.configureTestingModule({ imports: [MatChipsModule], declarations: [SingleChip], providers: [ - {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions}, {provide: Directionality, useFactory: () => ({ value: dir, change: new Subject() @@ -269,6 +265,10 @@ describe('MDC-based Option Chips', () => { expect(chipNativeElement.getAttribute('aria-disabled')).toBe('true'); }); }); + + it('should have a focus indicator', () => { + expect(chipNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true); + }); }); }); diff --git a/src/material-experimental/mdc-chips/chip-option.ts b/src/material-experimental/mdc-chips/chip-option.ts index 8dc011468e8d..db4dd4b7a3d7 100644 --- a/src/material-experimental/mdc-chips/chip-option.ts +++ b/src/material-experimental/mdc-chips/chip-option.ts @@ -42,6 +42,7 @@ export class MatChipSelectionChange { inputs: ['color', 'disableRipple', 'tabIndex'], host: { 'role': 'option', + 'class': 'mat-mdc-focus-indicator', '[class.mat-mdc-chip-disabled]': 'disabled', '[class.mat-mdc-chip-highlighted]': 'highlighted', '[class.mat-mdc-chip-with-avatar]': 'leadingIcon', diff --git a/src/material-experimental/mdc-chips/chip-remove.spec.ts b/src/material-experimental/mdc-chips/chip-remove.spec.ts index ab85e2160fb2..0be2f332b361 100644 --- a/src/material-experimental/mdc-chips/chip-remove.spec.ts +++ b/src/material-experimental/mdc-chips/chip-remove.spec.ts @@ -150,6 +150,11 @@ describe('MDC-based Chip Remove', () => { expect(event.defaultPrevented).toBe(false); }); + it('should have a focus indicator', () => { + const buttonElement = chipNativeElement.querySelector('button')!; + + expect(buttonElement.classList.contains('mat-mdc-focus-indicator')).toBe(true); + }); }); }); diff --git a/src/material-experimental/mdc-chips/chip-row.html b/src/material-experimental/mdc-chips/chip-row.html index b6e70e0f4aa1..be53f1f56550 100644 --- a/src/material-experimental/mdc-chips/chip-row.html +++ b/src/material-experimental/mdc-chips/chip-row.html @@ -1,6 +1,14 @@ + + + +
-
- +
diff --git a/src/material-experimental/mdc-chips/chip-row.spec.ts b/src/material-experimental/mdc-chips/chip-row.spec.ts index e3255903a303..8c561811a93a 100644 --- a/src/material-experimental/mdc-chips/chip-row.spec.ts +++ b/src/material-experimental/mdc-chips/chip-row.spec.ts @@ -7,7 +7,6 @@ import { } from '@angular/cdk/testing/private'; import {Component, DebugElement, ViewChild} from '@angular/core'; import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MatChipEvent, MatChipGrid, MatChipRow, MatChipsModule} from './index'; @@ -18,17 +17,14 @@ describe('MDC-based Row Chips', () => { let chipDebugElement: DebugElement; let chipNativeElement: HTMLElement; let chipInstance: MatChipRow; - let globalRippleOptions: RippleGlobalOptions; let dir = 'ltr'; beforeEach(async(() => { - globalRippleOptions = {}; TestBed.configureTestingModule({ imports: [MatChipsModule], declarations: [SingleChip], providers: [ - {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions}, {provide: Directionality, useFactory: () => ({ value: dir, change: new Subject() diff --git a/src/material-experimental/mdc-chips/chip.html b/src/material-experimental/mdc-chips/chip.html index 35e5f40a5c2e..73c38b4b4f6d 100644 --- a/src/material-experimental/mdc-chips/chip.html +++ b/src/material-experimental/mdc-chips/chip.html @@ -1,4 +1,11 @@ + + +
diff --git a/src/material-experimental/mdc-chips/chip.spec.ts b/src/material-experimental/mdc-chips/chip.spec.ts index d4c9b7125913..870293f99f9b 100644 --- a/src/material-experimental/mdc-chips/chip.spec.ts +++ b/src/material-experimental/mdc-chips/chip.spec.ts @@ -2,7 +2,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {createFakeEvent} from '@angular/cdk/testing/private'; import {Component, DebugElement, ViewChild} from '@angular/core'; import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core'; +import {MatRipple} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MatChip, MatChipEvent, MatChipSet, MatChipsModule} from './index'; @@ -13,17 +13,16 @@ describe('MDC-based MatChip', () => { let chipDebugElement: DebugElement; let chipNativeElement: HTMLElement; let chipInstance: MatChip; - let globalRippleOptions: RippleGlobalOptions; + let chipRippleDebugElement: DebugElement; + let chipRippleInstance: MatRipple; let dir = 'ltr'; beforeEach(async(() => { - globalRippleOptions = {}; TestBed.configureTestingModule({ imports: [MatChipsModule], declarations: [BasicChip, SingleChip], providers: [ - {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions}, {provide: Directionality, useFactory: () => ({ value: dir, change: new Subject() @@ -35,7 +34,6 @@ describe('MDC-based MatChip', () => { })); describe('MatBasicChip', () => { - beforeEach(() => { fixture = TestBed.createComponent(BasicChip); fixture.detectChanges(); @@ -43,11 +41,17 @@ describe('MDC-based MatChip', () => { chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!; chipNativeElement = chipDebugElement.nativeElement; chipInstance = chipDebugElement.injector.get(MatChip); + chipRippleDebugElement = chipDebugElement.query(By.directive(MatRipple))!; + chipRippleInstance = chipRippleDebugElement.injector.get(MatRipple); }); it('adds the `mat-mdc-basic-chip` class', () => { expect(chipNativeElement.classList).toContain('mat-mdc-basic-chip'); }); + + it('should have its ripple disabled', () => { + expect(chipRippleInstance.disabled).toBe(true, 'Expected basic chip ripples to be disabled.'); + }); }); describe('MatChip', () => { @@ -60,6 +64,8 @@ describe('MDC-based MatChip', () => { chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!; chipNativeElement = chipDebugElement.nativeElement; chipInstance = chipDebugElement.injector.get(MatChip); + chipRippleDebugElement = chipDebugElement.query(By.directive(MatRipple))!; + chipRippleInstance = chipRippleDebugElement.injector.get(MatRipple); testComponent = fixture.debugElement.componentInstance; }); @@ -115,12 +121,22 @@ describe('MDC-based MatChip', () => { 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.'); + it('should be able to disable ripples with the `[rippleDisabled]` input', () => { + expect(chipRippleInstance.disabled).toBe(false, 'Expected chip ripples to be enabled.'); + + testComponent.rippleDisabled = true; + fixture.detectChanges(); + + expect(chipRippleInstance.disabled).toBe(true, 'Expected chip ripples to be disabled.'); + }); + + it('should disable ripples when the chip is disabled', () => { + expect(chipRippleInstance.disabled).toBe(false, 'Expected chip ripples to be enabled.'); - globalRippleOptions.disabled = true; + testComponent.disabled = true; + fixture.detectChanges(); - expect(chipInstance.rippleDisabled).toBe(true, 'Expected chip ripples to be disabled.'); + expect(chipRippleInstance.disabled).toBe(true, 'Expected chip ripples to be disabled.'); }); it('should update the aria-label for disabled chips', () => { @@ -163,7 +179,7 @@ describe('MDC-based MatChip', () => { + (removed)="chipRemove($event)" [value]="value" [disableRipple]="rippleDisabled"> {{name}}
@@ -177,6 +193,7 @@ class SingleChip { removable: boolean = true; shouldShow: boolean = true; value: any; + rippleDisabled: boolean = false; chipFocus: (event?: MatChipEvent) => void = () => {}; chipDestroy: (event?: MatChipEvent) => void = () => {}; diff --git a/src/material-experimental/mdc-chips/chip.ts b/src/material-experimental/mdc-chips/chip.ts index 7983d5c9c5f2..f9367f8ee2b7 100644 --- a/src/material-experimental/mdc-chips/chip.ts +++ b/src/material-experimental/mdc-chips/chip.ts @@ -8,7 +8,6 @@ import {Directionality} from '@angular/cdk/bidi'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; -import {Platform} from '@angular/cdk/platform'; import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; import { AfterContentInit, @@ -20,29 +19,28 @@ import { Directive, ElementRef, EventEmitter, + HostListener, Inject, Input, NgZone, OnDestroy, Optional, Output, - ViewEncapsulation, HostListener + ViewEncapsulation, + ViewChild, } from '@angular/core'; import { CanColor, CanColorCtor, CanDisableRipple, CanDisableRippleCtor, - MAT_RIPPLE_GLOBAL_OPTIONS, HasTabIndex, HasTabIndexCtor, + MatRipple, mixinColor, mixinDisableRipple, mixinTabIndex, - RippleConfig, - RippleGlobalOptions, - RippleRenderer, - RippleTarget, + RippleAnimationConfig, } from '@angular/material/core'; import {MDCChipAdapter, MDCChipFoundation} from '@material/chips'; import {numbers} from '@material/ripple'; @@ -114,7 +112,16 @@ const _MatChipMixinBase: changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatChip extends _MatChipMixinBase implements AfterContentInit, AfterViewInit, - CanColor, CanDisableRipple, HasTabIndex, RippleTarget, OnDestroy { + CanColor, CanDisableRipple, HasTabIndex, OnDestroy { + /** The ripple animation configuration to use for the chip. */ + readonly _rippleAnimation: RippleAnimationConfig = { + enterDuration: numbers.DEACTIVATION_TIMEOUT_MS, + exitDuration: numbers.FG_DEACTIVATION_MS + }; + + /** Whether the ripple is centered on the chip. */ + readonly _isRippleCentered = false; + /** Emits when the chip is focused. */ readonly _onFocus = new Subject(); @@ -212,25 +219,6 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte /** Subject that emits when the component has been destroyed. */ protected _destroyed = new Subject(); - /** The ripple renderer for this chip. */ - private _rippleRenderer: RippleRenderer; - - /** - * Ripple configuration for ripples that are launched on pointer down. - * Implemented as part of RippleTarget. - * @docs-private - */ - rippleConfig: RippleConfig & RippleGlobalOptions; - - /** - * Implemented as part of RippleTarget. Whether ripples are disabled on interaction. - * @docs-private - */ - get rippleDisabled(): boolean { - return this.disabled || this.disableRipple || !!this.rippleConfig.disabled || - this._isBasicChip(); - } - /** The chip's leading icon. */ @ContentChild(MatChipAvatar) leadingIcon: MatChipAvatar; @@ -240,6 +228,9 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte /** The chip's trailing remove icon. */ @ContentChild(MatChipRemove) removeIcon: MatChipRemove; + /** Reference to the MatRipple instance of the chip. */ + @ViewChild(MatRipple) ripple: MatRipple; + /** * Implementation of the MDC chip adapter interface. * These methods are called by the chip foundation. @@ -314,10 +305,7 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte constructor( public _changeDetectorRef: ChangeDetectorRef, readonly _elementRef: ElementRef, - private _platform: Platform, protected _ngZone: NgZone, - @Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) - private _globalRippleOptions: RippleGlobalOptions | null, @Optional() private _dir: Directionality, // @breaking-change 8.0.0 `animationMode` parameter to become required. @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { @@ -331,7 +319,6 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte } ngAfterViewInit() { - this._initRipple(); this._chipFoundation.init(); } @@ -339,7 +326,6 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte this.destroyed.emit({chip: this}); this._destroyed.next(); this._destroyed.complete(); - this._rippleRenderer._removeTriggerEvents(); this._chipFoundation.destroy(); } @@ -406,21 +392,6 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte this._changeDetectorRef.markForCheck(); } - /** Initializes the ripple renderer. */ - private _initRipple() { - this.rippleConfig = this._globalRippleOptions || {}; - - // Configure ripple animation to match MDC Ripple. - this.rippleConfig.animation = { - enterDuration: numbers.DEACTIVATION_TIMEOUT_MS, - exitDuration: numbers.FG_DEACTIVATION_MS, - }; - - this._rippleRenderer = - new RippleRenderer(this, this._ngZone, this._elementRef, this._platform); - this._rippleRenderer.setupTriggerEvents(this._elementRef); - } - /** Forwards interaction events to the MDC chip foundation. */ _handleInteraction(event: MouseEvent | KeyboardEvent) { if (!this.disabled) { @@ -428,6 +399,11 @@ export class MatChip extends _MatChipMixinBase implements AfterContentInit, Afte } } + /** Whether or not the ripple should be disabled. */ + _isRippleDisabled(): boolean { + return this.disabled || this.disableRipple || this._isBasicChip(); + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_removable: BooleanInput; static ngAcceptInputType_highlighted: BooleanInput; diff --git a/src/material-experimental/mdc-chips/chips.scss b/src/material-experimental/mdc-chips/chips.scss index 16ca7f771602..36e9f1721632 100644 --- a/src/material-experimental/mdc-chips/chips.scss +++ b/src/material-experimental/mdc-chips/chips.scss @@ -11,10 +11,6 @@ .mat-mdc-chip { // MDC uses a pointer cursor cursor: default; - // Needed to prevent mat-ripple from escaping the chip - overflow: hidden; - // Required for the ripple to clip properly in Safari. - transform: translateZ(0); @include cdk-high-contrast { outline: solid 1px; @@ -26,6 +22,20 @@ } } +// The ripple container should match the bounds of the entire chip. +.mat-mdc-chip-ripple { + @include mat-fill; + + // Disable pointer events for the ripple container and state overlay because the container + // will overlay the user content and we don't want to disable mouse events on the user content. + // Pointer events can be safely disabled because the ripple trigger element is the host element. + pointer-events: none; + + // Inherit the border radius from the parent so that state overlay and ripples don't exceed the + // parent button boundaries. + border-radius: inherit; +} + // The MDC chip styles related to hover and focus states are intertwined with the MDC ripple styles. // We currently don't use the MDC ripple due to size concerns, therefore we need to add some // additional styles to restore these states. @@ -37,6 +47,7 @@ content: ''; pointer-events: none; opacity: 0; + border-radius: inherit; } } @@ -80,3 +91,8 @@ input.mat-mdc-chip-input { stroke: #000 !important; } } + +// Needed for the focus indicator. +.mat-chip-row-focusable-text-content { + position: relative; +} diff --git a/src/material-experimental/mdc-chips/module.ts b/src/material-experimental/mdc-chips/module.ts index c1b8b20e5b92..a24ab32ec51a 100644 --- a/src/material-experimental/mdc-chips/module.ts +++ b/src/material-experimental/mdc-chips/module.ts @@ -9,7 +9,7 @@ import {ENTER} from '@angular/cdk/keycodes'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {ErrorStateMatcher, MatCommonModule} from '@angular/material/core'; +import {ErrorStateMatcher, MatCommonModule, MatRippleModule} from '@angular/material/core'; import {MatChip, MatChipCssInternalOnly} from './chip'; import {MAT_CHIPS_DEFAULT_OPTIONS, MatChipsDefaultOptions} from './chip-default-options'; import {MatChipGrid} from './chip-grid'; @@ -36,7 +36,7 @@ const CHIP_DECLARATIONS = [ ]; @NgModule({ - imports: [MatCommonModule, CommonModule], + imports: [MatCommonModule, CommonModule, MatRippleModule], exports: CHIP_DECLARATIONS, declarations: CHIP_DECLARATIONS, providers: [ diff --git a/src/material-experimental/mdc-helpers/_mdc-helpers.scss b/src/material-experimental/mdc-helpers/_mdc-helpers.scss index 299ee3ae495c..cb5e65f109fc 100644 --- a/src/material-experimental/mdc-helpers/_mdc-helpers.scss +++ b/src/material-experimental/mdc-helpers/_mdc-helpers.scss @@ -236,11 +236,17 @@ $mat-typography-level-mappings: ( // contrastive and renders appropriately. .mat-mdc-focus-indicator.mdc-button::before, + .mat-mdc-focus-indicator.mdc-chip::before, .mat-mdc-focus-indicator.mdc-fab::before, .mat-mdc-focus-indicator.mdc-icon-button::before { margin: $mat-focus-indicator-border-width * -2; } + .mat-mdc-focus-indicator.mat-mdc-chip-remove::before, + .mat-mdc-focus-indicator.mat-chip-row-focusable-text-content::before { + margin: $mat-focus-indicator-border-width * -1; + } + .mat-mdc-focus-indicator.mat-mdc-tab::before, .mat-mdc-focus-indicator.mat-mdc-tab-link::before { margin: $mat-focus-indicator-border-width * 2;