Skip to content

Commit

Permalink
refactor(material/core): generalize lazy ripple logic (angular#26897)
Browse files Browse the repository at this point in the history
* refactor(material/core): generalize lazy ripple logic

* fixup! refactor(material/core): generalize lazy ripple logic

* fixup! refactor(material/core): generalize lazy ripple logic

* fixup! refactor(material/core): generalize lazy ripple logic

* fixup! refactor(material/core): generalize lazy ripple logic
  • Loading branch information
wagnermaciel authored Jun 12, 2023
1 parent f74ade4 commit 18400e7
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 198 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
/src/material/core/mdc-helpers/** @mmalerba
/src/material/core/option/** @crisbeto
/src/material/core/placeholder/** @mmalerba
/src/material/core/private/** @wagnermaciel
/src/material/core/ripple/** @devversion
/src/material/core/selection/** @andrewseguin
/src/material/core/selection/pseudo*/** @crisbeto @andrewseguin
Expand Down
57 changes: 38 additions & 19 deletions src/material/button/button-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
*/

import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Platform} from '@angular/cdk/platform';
import {
AfterViewInit,
Directive,
ElementRef,
inject,
NgZone,
OnChanges,
OnDestroy,
OnInit,
} from '@angular/core';
Expand All @@ -26,8 +26,8 @@ import {
mixinColor,
mixinDisabled,
mixinDisableRipple,
MatRippleLoader,
} from '@angular/material/core';
import {MAT_BUTTON_RIPPLE_UNINITIALIZED, MatButtonLazyLoader} from './button-lazy-loader';

/** Inputs common to all buttons. */
export const MAT_BUTTON_INPUTS = ['disabled', 'disableRipple', 'color'];
Expand All @@ -43,7 +43,6 @@ export const MAT_BUTTON_HOST = {
// Add a class that applies to all buttons. This makes it easier to target if somebody
// wants to target all Material buttons.
'[class.mat-mdc-button-base]': 'true',
[MAT_BUTTON_RIPPLE_UNINITIALIZED]: '',
};

/** List of classes to add to buttons instances based on host attribute selector. */
Expand Down Expand Up @@ -94,15 +93,15 @@ export const _MatButtonMixin = mixinColor(
@Directive()
export class MatButtonBase
extends _MatButtonMixin
implements CanDisable, CanColor, CanDisableRipple, AfterViewInit, OnChanges, OnDestroy
implements CanDisable, CanColor, CanDisableRipple, AfterViewInit, OnDestroy
{
private readonly _focusMonitor = inject(FocusMonitor);

/**
* Handles the lazy creation of the MatButton ripple.
* Used to improve initial load time of large applications.
*/
_rippleLoader: MatButtonLazyLoader = inject(MatButtonLazyLoader);
_rippleLoader: MatRippleLoader = inject(MatRippleLoader);

/** Whether this button is a FAB. Used to apply the correct class on the ripple. */
_isFab = false;
Expand All @@ -113,17 +112,33 @@ export class MatButtonBase
* @breaking-change 17.0.0
*/
get ripple(): MatRipple {
if (!this._ripple && this._rippleLoader) {
this._ripple = this._rippleLoader._createMatRipple(this._elementRef.nativeElement);
}
return this._ripple!;
return this._rippleLoader?.getRipple(this._elementRef.nativeElement)!;
}
set ripple(v: MatRipple) {
this._ripple = v;
this._rippleLoader?.attachRipple(this._elementRef.nativeElement, v);
}

/** @docs-private Reference to the MatRipple instance of the button. */
protected _ripple?: MatRipple;
// We override `disableRipple` and `disabled` so we can hook into
// their setters and update the ripple disabled state accordingly.

/** Whether the ripple effect is disabled or not. */
override get disableRipple(): boolean {
return this._disableRipple;
}
override set disableRipple(value: any) {
this._disableRipple = coerceBooleanProperty(value);
this._updateRippleDisabled();
}
private _disableRipple: boolean = false;

override get disabled(): boolean {
return this._disabled;
}
override set disabled(value: any) {
this._disabled = coerceBooleanProperty(value);
this._updateRippleDisabled();
}
private _disabled: boolean = false;

constructor(
elementRef: ElementRef,
Expand All @@ -133,6 +148,10 @@ export class MatButtonBase
) {
super(elementRef);

this._rippleLoader?.configureRipple(this._elementRef.nativeElement, {
className: 'mat-mdc-button-ripple',
});

const classList = (elementRef.nativeElement as HTMLElement).classList;

// For each of the variant selectors that is present in the button's host
Expand All @@ -150,12 +169,6 @@ export class MatButtonBase
this._focusMonitor.monitor(this._elementRef, true);
}

ngOnChanges() {
if (this._ripple) {
this._ripple.disabled = this.disableRipple || this.disabled;
}
}

ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
}
Expand All @@ -173,6 +186,13 @@ export class MatButtonBase
private _hasHostAttributes(...attributes: string[]) {
return attributes.some(attribute => this._elementRef.nativeElement.hasAttribute(attribute));
}

private _updateRippleDisabled(): void {
this._rippleLoader?.setDisabled(
this._elementRef.nativeElement,
this.disableRipple || this.disabled,
);
}
}

/** Shared inputs by buttons using the `<a>` tag */
Expand All @@ -195,7 +215,6 @@ export const MAT_ANCHOR_HOST = {
// Add a class that applies to all buttons. This makes it easier to target if somebody
// wants to target all Material buttons.
'[class.mat-mdc-button-base]': 'true',
[MAT_BUTTON_RIPPLE_UNINITIALIZED]: '',
};

/**
Expand Down
151 changes: 0 additions & 151 deletions src/material/button/button-lazy-loader.ts

This file was deleted.

11 changes: 0 additions & 11 deletions src/material/button/button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {By} from '@angular/platform-browser';
import {MatButtonModule, MatButton, MatFabDefaultOptions, MAT_FAB_DEFAULT_OPTIONS} from './index';
import {MatRipple, ThemePalette} from '@angular/material/core';
import {createMouseEvent, dispatchEvent} from '@angular/cdk/testing/private';
import {MAT_BUTTON_RIPPLE_UNINITIALIZED} from './button-lazy-loader';

describe('MDC-based MatButton', () => {
beforeEach(waitForAsync(() => {
Expand Down Expand Up @@ -317,9 +316,6 @@ describe('MDC-based MatButton', () => {
const fab = fixture.debugElement.query(By.css('button[mat-fab]'))!;
let ripple = fab.nativeElement.querySelector('.mat-mdc-button-ripple');
expect(ripple).withContext('Expect ripple to be absent before user interaction').toBeNull();
expect(fab.nativeElement.hasAttribute(MAT_BUTTON_RIPPLE_UNINITIALIZED))
.withContext('Expect mat-button to have the "uninitialized" attr before user interaction')
.toBeTrue();

// Referencing the ripple should instantiate the ripple.
expect(fab.componentInstance.ripple).toBeDefined();
Expand All @@ -328,11 +324,6 @@ describe('MDC-based MatButton', () => {
expect(ripple)
.withContext('Expect ripple to be present after user interaction')
.not.toBeNull();
expect(fab.nativeElement.hasAttribute(MAT_BUTTON_RIPPLE_UNINITIALIZED))
.withContext(
'Expect mat-button NOT to have the "uninitialized" attr after user interaction',
)
.toBeFalse();
});

// Ensure each of these events triggers the initialization of the button ripple.
Expand All @@ -341,12 +332,10 @@ describe('MDC-based MatButton', () => {
const fab = fixture.debugElement.query(By.css('button[mat-fab]'))!;
let ripple = fab.nativeElement.querySelector('.mat-mdc-button-ripple');
expect(ripple).toBeNull();
expect(fab.nativeElement.hasAttribute(MAT_BUTTON_RIPPLE_UNINITIALIZED)).toBeTrue();

dispatchEvent(fab.nativeElement, createMouseEvent(event));
ripple = fab.nativeElement.querySelector('.mat-mdc-button-ripple');
expect(ripple).not.toBeNull();
expect(fab.nativeElement.hasAttribute(MAT_BUTTON_RIPPLE_UNINITIALIZED)).toBeFalse();
});
}
});
Expand Down
16 changes: 2 additions & 14 deletions src/material/button/icon-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
MatAnchorBase,
MatButtonBase,
} from './button-base';
import {MatRipple} from '@angular/material/core';

/**
* Material Design icon button component. This type of button displays a single interactive icon for
Expand All @@ -44,26 +43,15 @@ import {MatRipple} from '@angular/material/core';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatIconButton extends MatButtonBase {
/**
* Reference to the MatRipple instance of the button.
* @deprecated Considered an implementation detail. To be removed.
* @breaking-change 17.0.0
*/
override get ripple(): MatRipple {
if (!this._ripple && this._rippleLoader) {
this._ripple = this._rippleLoader._createMatRipple(this._elementRef.nativeElement);
this._ripple!.centered = true;
}
return this._ripple!;
}

constructor(
elementRef: ElementRef,
platform: Platform,
ngZone: NgZone,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
) {
super(elementRef, platform, ngZone, animationMode);

this._rippleLoader.configureRipple(this._elementRef.nativeElement, {centered: true});
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/material/core/private/index.ts
Original file line number Diff line number Diff line change
@@ -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 {MatRippleLoader} from './ripple-loader';
Loading

0 comments on commit 18400e7

Please sign in to comment.