mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(slide-toggle): make slide-toggle click and drag actions configurable (#11719)
This commit is contained in:
@@ -8,4 +8,4 @@
|
||||
|
||||
export * from './slide-toggle-module';
|
||||
export * from './slide-toggle';
|
||||
|
||||
export * from './slide-toggle-config';
|
||||
|
||||
@@ -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 {InjectionToken} from '@angular/core';
|
||||
|
||||
|
||||
/** Default `mat-slide-toggle` options that can be overridden. */
|
||||
export interface MatSlideToggleDefaultOptions {
|
||||
/** Whether toggle action triggers value changes in slide toggle. */
|
||||
disableToggleValue?: boolean;
|
||||
/** Whether drag action triggers value changes in slide toggle. */
|
||||
disableDragValue?: boolean;
|
||||
}
|
||||
|
||||
/** Injection token to be used to override the default options for `mat-slide-toggle`. */
|
||||
export const MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS =
|
||||
new InjectionToken<MatSlideToggleDefaultOptions>('mat-slide-toggle-default-options', {
|
||||
providedIn: 'root',
|
||||
factory: () => ({disableToggleValue: false, disableDragValue: false})
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/f
|
||||
import {defaultRippleAnimationConfig} from '@angular/material/core';
|
||||
import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
|
||||
import {TestGestureConfig} from '../slider/test-gesture-config';
|
||||
import {MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS} from './slide-toggle-config';
|
||||
import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index';
|
||||
|
||||
describe('MatSlideToggle without forms', () => {
|
||||
@@ -355,6 +356,96 @@ describe('MatSlideToggle without forms', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('custom action configuration', () => {
|
||||
it('should not change value on click when click action is noop', fakeAsync(() => {
|
||||
TestBed
|
||||
.resetTestingModule()
|
||||
.configureTestingModule({
|
||||
imports: [MatSlideToggleModule],
|
||||
declarations: [SlideToggleBasic],
|
||||
providers: [
|
||||
{
|
||||
provide: HAMMER_GESTURE_CONFIG,
|
||||
useFactory: () => gestureConfig = new TestGestureConfig()
|
||||
},
|
||||
{provide: MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS, useValue: {disableToggleValue: true}},
|
||||
]
|
||||
});
|
||||
const fixture = TestBed.createComponent(SlideToggleBasic);
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const slideToggleDebug = fixture.debugElement.query(By.css('mat-slide-toggle'));
|
||||
|
||||
const slideToggle = slideToggleDebug.componentInstance;
|
||||
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
const labelElement = fixture.debugElement.query(By.css('label')).nativeElement;
|
||||
|
||||
expect(testComponent.toggleTriggered).toBe(0);
|
||||
expect(testComponent.dragTriggered).toBe(0);
|
||||
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
|
||||
|
||||
labelElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
|
||||
expect(testComponent.toggleTriggered).toBe(1, 'Expect toggle once');
|
||||
expect(testComponent.dragTriggered).toBe(0);
|
||||
|
||||
inputElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
|
||||
expect(testComponent.toggleTriggered).toBe(2, 'Expect toggle twice');
|
||||
expect(testComponent.dragTriggered).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not change value on dragging when drag action is noop', fakeAsync(() => {
|
||||
TestBed
|
||||
.resetTestingModule()
|
||||
.configureTestingModule({
|
||||
imports: [MatSlideToggleModule],
|
||||
declarations: [SlideToggleBasic],
|
||||
providers: [
|
||||
{
|
||||
provide: HAMMER_GESTURE_CONFIG,
|
||||
useFactory: () => gestureConfig = new TestGestureConfig()
|
||||
},
|
||||
{provide: MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS, useValue: {disableDragValue: true}},
|
||||
]
|
||||
});
|
||||
const fixture = TestBed.createComponent(SlideToggleBasic);
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const slideToggleDebug = fixture.debugElement.query(By.css('mat-slide-toggle'));
|
||||
const thumbContainerDebug = slideToggleDebug
|
||||
.query(By.css('.mat-slide-toggle-thumb-container'));
|
||||
|
||||
const slideThumbContainer = thumbContainerDebug.nativeElement;
|
||||
const slideToggle = slideToggleDebug.componentInstance;
|
||||
|
||||
expect(testComponent.toggleTriggered).toBe(0);
|
||||
expect(testComponent.dragTriggered).toBe(0);
|
||||
expect(slideToggle.checked).toBe(false);
|
||||
|
||||
gestureConfig.emitEventForElement('slidestart', slideThumbContainer);
|
||||
|
||||
expect(slideThumbContainer.classList).toContain('mat-dragging');
|
||||
|
||||
gestureConfig.emitEventForElement('slide', slideThumbContainer, {
|
||||
deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
|
||||
});
|
||||
|
||||
gestureConfig.emitEventForElement('slideend', slideThumbContainer);
|
||||
|
||||
// Flush the timeout for the slide ending.
|
||||
tick();
|
||||
|
||||
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
|
||||
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
|
||||
expect(testComponent.lastEvent).toBeUndefined();
|
||||
expect(testComponent.toggleTriggered).toBe(0);
|
||||
expect(testComponent.dragTriggered).toBe(1, 'Expect drag once');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('with dragging', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
@@ -849,6 +940,8 @@ describe('MatSlideToggle with forms', () => {
|
||||
[tabIndex]="slideTabindex"
|
||||
[labelPosition]="labelPosition"
|
||||
[disableRipple]="disableRipple"
|
||||
(toggleChange)="onSlideToggleChange()"
|
||||
(dragChange)="onSlideDragChange()"
|
||||
(change)="onSlideChange($event)"
|
||||
(click)="onSlideClick($event)">
|
||||
<span>Test Slide Toggle</span>
|
||||
@@ -867,9 +960,13 @@ class SlideToggleBasic {
|
||||
slideTabindex: number;
|
||||
lastEvent: MatSlideToggleChange;
|
||||
labelPosition: string;
|
||||
toggleTriggered: number = 0;
|
||||
dragTriggered: number = 0;
|
||||
|
||||
onSlideClick: (event?: Event) => void = () => {};
|
||||
onSlideChange = (event: MatSlideToggleChange) => this.lastEvent = event;
|
||||
onSlideToggleChange = () => this.toggleTriggered++;
|
||||
onSlideDragChange = () => this.dragTriggered++;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -42,6 +42,10 @@ import {
|
||||
RippleRef,
|
||||
} from '@angular/material/core';
|
||||
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
|
||||
import {
|
||||
MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS,
|
||||
MatSlideToggleDefaultOptions
|
||||
} from './slide-toggle-config';
|
||||
|
||||
// Increasing integer for generating unique ids for slide-toggle components.
|
||||
let nextUniqueId = 0;
|
||||
@@ -153,6 +157,21 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
@Output() readonly change: EventEmitter<MatSlideToggleChange> =
|
||||
new EventEmitter<MatSlideToggleChange>();
|
||||
|
||||
/**
|
||||
* An event will be dispatched each time the slide-toggle input is toggled.
|
||||
* This event always fire when user toggle the slide toggle, but does not mean the slide toggle's
|
||||
* value is changed. The event does not fire when user drag to change the slide toggle value.
|
||||
*/
|
||||
@Output() readonly toggleChange: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* An event will be dispatched each time the slide-toggle is dragged.
|
||||
* This event always fire when user drag the slide toggle to make a change that greater than 50%.
|
||||
* It does not mean the slide toggle's value is changed. The event does not fire when user toggle
|
||||
* the slide toggle to change the slide toggle's value.
|
||||
*/
|
||||
@Output() readonly dragChange: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/** Returns the unique id for the visual hidden input. */
|
||||
get inputId(): string { return `${this.id || this._uniqueId}-input`; }
|
||||
|
||||
@@ -172,8 +191,9 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Attribute('tabindex') tabIndex: string,
|
||||
private _ngZone: NgZone,
|
||||
@Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS)
|
||||
public defaults: MatSlideToggleDefaultOptions,
|
||||
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) {
|
||||
|
||||
super(elementRef);
|
||||
this.tabIndex = parseInt(tabIndex) || 0;
|
||||
}
|
||||
@@ -195,10 +215,15 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
// emit its event object to the component's `change` output.
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this._dragging) {
|
||||
this.toggleChange.emit();
|
||||
}
|
||||
// Releasing the pointer over the `<label>` element while dragging triggers another
|
||||
// click event on the `<label>` element. This means that the checked state of the underlying
|
||||
// input changed unintentionally and needs to be changed back.
|
||||
if (this._dragging) {
|
||||
// input changed unintentionally and needs to be changed back. Or when the slide toggle's config
|
||||
// disabled toggle change event by setting `disableToggleValue: true`, the slide toggle's value
|
||||
// does not change, and the checked state of the underlying input needs to be changed back.
|
||||
if (this._dragging || this.defaults.disableToggleValue) {
|
||||
this._inputElement.nativeElement.checked = this.checked;
|
||||
return;
|
||||
}
|
||||
@@ -316,8 +341,11 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
|
||||
const newCheckedValue = this._dragPercentage > 50;
|
||||
|
||||
if (newCheckedValue !== this.checked) {
|
||||
this.checked = newCheckedValue;
|
||||
this._emitChangeEvent();
|
||||
this.dragChange.emit();
|
||||
if (!this.defaults.disableDragValue) {
|
||||
this.checked = newCheckedValue;
|
||||
this._emitChangeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
// The drag should be stopped outside of the current event handler, otherwise the
|
||||
|
||||
Reference in New Issue
Block a user