mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat: add ripples to button-toggle (#9891)
* Adds ripples to the `MatButtonToggle` component * Fixes that the focus overlay for button toggles shows on mouse/touch press. * Properly stops monitoring through the `FocusMonitor` on component destroy. * Removes unnecessary mixin for focus-overlay color. Color can be always set, because the overlay will be toggled through `opacity` (performance improvement) Closes #9442
This commit is contained in:
committed by
Jeremy Elbourn
parent
66a01fb383
commit
53417d49ed
@@ -10,10 +10,18 @@
|
||||
|
||||
<section class="demo-section">
|
||||
<mat-button-toggle-group name="alignment" [vertical]="isVertical">
|
||||
<mat-button-toggle value="left"><mat-icon>format_align_left</mat-icon></mat-button-toggle>
|
||||
<mat-button-toggle value="center"><mat-icon>format_align_center</mat-icon></mat-button-toggle>
|
||||
<mat-button-toggle value="right"><mat-icon>format_align_right</mat-icon></mat-button-toggle>
|
||||
<mat-button-toggle value="justify" [disabled]="isDisabled"><mat-icon>format_align_justify</mat-icon></mat-button-toggle>
|
||||
<mat-button-toggle value="left"[disabled]="isDisabled">
|
||||
<mat-icon>format_align_left</mat-icon>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="center" [disabled]="isDisabled">
|
||||
<mat-icon>format_align_center</mat-icon>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="right" [disabled]="isDisabled">
|
||||
<mat-icon>format_align_right</mat-icon>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="justify" [disabled]="isDisabled">
|
||||
<mat-icon>format_align_justify</mat-icon>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
@import '../core/theming/theming';
|
||||
@import '../core/typography/typography-utils';
|
||||
|
||||
// Applies a focus style to an mat-button-toggle element for each of the supported palettes.
|
||||
@mixin _mat-button-toggle-focus-color($theme) {
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.mat-button-toggle-focus-overlay {
|
||||
background-color: mat-color($background, focused-button);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mat-button-toggle-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
@@ -18,8 +9,8 @@
|
||||
.mat-button-toggle {
|
||||
color: mat-color($foreground, hint-text);
|
||||
|
||||
&.cdk-focused {
|
||||
@include _mat-button-toggle-focus-color($theme);
|
||||
.mat-button-toggle-focus-overlay {
|
||||
background-color: mat-color($background, focused-button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {MatButtonToggleGroup, MatButtonToggleGroupMultiple, MatButtonToggle} from './button-toggle';
|
||||
import {MatCommonModule} from '@angular/material/core';
|
||||
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
|
||||
import {UNIQUE_SELECTION_DISPATCHER_PROVIDER} from '@angular/cdk/collections';
|
||||
import {A11yModule} from '@angular/cdk/a11y';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [MatCommonModule, A11yModule],
|
||||
imports: [MatCommonModule, MatRippleModule, A11yModule],
|
||||
exports: [
|
||||
MatButtonToggleGroup,
|
||||
MatButtonToggleGroupMultiple,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<label [attr.for]="inputId" class="mat-button-toggle-label">
|
||||
<label [attr.for]="inputId" class="mat-button-toggle-label" #label>
|
||||
<input #input class="mat-button-toggle-input cdk-visually-hidden"
|
||||
[type]="_type"
|
||||
[id]="inputId"
|
||||
@@ -15,3 +15,8 @@
|
||||
</div>
|
||||
</label>
|
||||
<div class="mat-button-toggle-focus-overlay"></div>
|
||||
|
||||
<div class="mat-button-toggle-ripple" matRipple
|
||||
[matRippleTrigger]="label"
|
||||
[matRippleDisabled]="this.disableRipple || this.disabled">
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,6 @@ $mat-button-toggle-border-radius: 2px !default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mat-button-toggle-disabled .mat-button-toggle-label-content {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -39,8 +38,11 @@ $mat-button-toggle-border-radius: 2px !default;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
|
||||
&.cdk-keyboard-focused,
|
||||
&.cdk-program-focused {
|
||||
// Similar to components like the checkbox, slide-toggle and radio, we cannot show the focus
|
||||
// overlay for `.cdk-program-focused` because mouse clicks on the <label> element would be always
|
||||
// treated as programmatic focus.
|
||||
// TODO(paul): support `program` as well. See https://github.com/angular/material2/issues/9889
|
||||
&.cdk-keyboard-focused {
|
||||
.mat-button-toggle-focus-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -68,3 +70,12 @@ $mat-button-toggle-border-radius: 2px !default;
|
||||
opacity: 0;
|
||||
@include mat-fill;
|
||||
}
|
||||
|
||||
.mat-button-toggle-ripple {
|
||||
@include mat-fill;
|
||||
|
||||
// Disable pointer events for the ripple container, because the container will overlay the user
|
||||
// content and we don't want to prevent mouse clicks that should toggle the state.
|
||||
// Pointer events can be safely disabled because the ripple trigger element is the label element.
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
} from '@angular/core/testing';
|
||||
import {dispatchMouseEvent} from '@angular/cdk/testing';
|
||||
import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
|
||||
import {Component, DebugElement} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
@@ -177,8 +178,32 @@ describe('MatButtonToggle with forms', () => {
|
||||
|
||||
expect(testComponent.modelValue).toBe('green');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should show a ripple on label click', () => {
|
||||
const groupElement = groupDebugElement.nativeElement;
|
||||
|
||||
expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
|
||||
dispatchMouseEvent(buttonToggleLabels[0], 'mousedown');
|
||||
dispatchMouseEvent(buttonToggleLabels[0], 'mouseup');
|
||||
|
||||
expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow ripples to be disabled', () => {
|
||||
const groupElement = groupDebugElement.nativeElement;
|
||||
|
||||
testComponent.disableRipple = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
|
||||
dispatchMouseEvent(buttonToggleLabels[0], 'mousedown');
|
||||
dispatchMouseEvent(buttonToggleLabels[0], 'mouseup');
|
||||
|
||||
expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MatButtonToggle without forms', () => {
|
||||
@@ -647,7 +672,8 @@ class ButtonTogglesInsideButtonToggleGroup {
|
||||
@Component({
|
||||
template: `
|
||||
<mat-button-toggle-group [(ngModel)]="modelValue" (change)="lastEvent = $event">
|
||||
<mat-button-toggle *ngFor="let option of options" [value]="option.value">
|
||||
<mat-button-toggle *ngFor="let option of options" [value]="option.value"
|
||||
[disableRipple]="disableRipple">
|
||||
{{option.label}}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
@@ -661,6 +687,7 @@ class ButtonToggleGroupWithNgModel {
|
||||
{label: 'Blue', value: 'blue'},
|
||||
];
|
||||
lastEvent: MatButtonToggleChange;
|
||||
disableRipple = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -28,7 +28,12 @@ import {
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
import {CanDisable, mixinDisabled} from '@angular/material/core';
|
||||
import {
|
||||
CanDisable,
|
||||
CanDisableRipple,
|
||||
mixinDisabled,
|
||||
mixinDisableRipple
|
||||
} from '@angular/material/core';
|
||||
|
||||
/** Acceptable types for a button toggle. */
|
||||
export type ToggleType = 'checkbox' | 'radio';
|
||||
@@ -225,6 +230,11 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
|
||||
private _vertical: boolean = false;
|
||||
}
|
||||
|
||||
// Boilerplate for applying mixins to the MatButtonToggle class.
|
||||
/** @docs-private */
|
||||
export class MatButtonToggleBase {}
|
||||
export const _MatButtonToggleMixinBase = mixinDisableRipple(MatButtonToggleBase);
|
||||
|
||||
/** Single button inside of a toggle group. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -235,6 +245,7 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
|
||||
preserveWhitespaces: false,
|
||||
exportAs: 'matButtonToggle',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
inputs: ['disableRipple'],
|
||||
host: {
|
||||
'[class.mat-button-toggle-standalone]': '!buttonToggleGroup && !buttonToggleGroupMultiple',
|
||||
'[class.mat-button-toggle-checked]': 'checked',
|
||||
@@ -243,7 +254,9 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
|
||||
'[attr.id]': 'id',
|
||||
}
|
||||
})
|
||||
export class MatButtonToggle implements OnInit, OnDestroy {
|
||||
export class MatButtonToggle extends _MatButtonToggleMixinBase
|
||||
implements OnInit, OnDestroy, CanDisableRipple {
|
||||
|
||||
/**
|
||||
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
|
||||
* take precedence so this may be omitted.
|
||||
@@ -331,6 +344,7 @@ export class MatButtonToggle implements OnInit, OnDestroy {
|
||||
private _buttonToggleDispatcher: UniqueSelectionDispatcher,
|
||||
private _elementRef: ElementRef,
|
||||
private _focusMonitor: FocusMonitor) {
|
||||
super();
|
||||
|
||||
this.buttonToggleGroup = toggleGroup;
|
||||
this.buttonToggleGroupMultiple = toggleGroupMultiple;
|
||||
|
||||
@@ -321,6 +321,7 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc
|
||||
|
||||
/** Function is called whenever the focus changes for the input element. */
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
this._focusRipple = this.ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
|
||||
@@ -593,6 +593,7 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
|
||||
|
||||
/** Function is called whenever the focus changes for the input element. */
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
|
||||
} else if (!focusOrigin) {
|
||||
|
||||
@@ -231,6 +231,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
|
||||
/** Function is called whenever the focus changes for the input element. */
|
||||
private _onInputFocusChange(focusOrigin: FocusOrigin) {
|
||||
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
|
||||
if (!this._focusRipple && focusOrigin === 'keyboard') {
|
||||
// For keyboard focus show a persistent ripple as focus indicator.
|
||||
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
|
||||
|
||||
Reference in New Issue
Block a user