From 9942a6e1c81d39383a2707f2dd0fea34314126d4 Mon Sep 17 00:00:00 2001 From: Yuan Gao Date: Fri, 8 Jun 2018 10:56:09 -0700 Subject: [PATCH] feat(slide-toggle): make slide-toggle click and drag actions configurable --- src/lib/slide-toggle/public-api.ts | 2 +- src/lib/slide-toggle/slide-toggle-config.ts | 24 +++++ src/lib/slide-toggle/slide-toggle.spec.ts | 97 +++++++++++++++++++++ src/lib/slide-toggle/slide-toggle.ts | 38 ++++++-- 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 src/lib/slide-toggle/slide-toggle-config.ts diff --git a/src/lib/slide-toggle/public-api.ts b/src/lib/slide-toggle/public-api.ts index 79b095fe1005..1598998e60f8 100644 --- a/src/lib/slide-toggle/public-api.ts +++ b/src/lib/slide-toggle/public-api.ts @@ -8,4 +8,4 @@ export * from './slide-toggle-module'; export * from './slide-toggle'; - +export * from './slide-toggle-config'; diff --git a/src/lib/slide-toggle/slide-toggle-config.ts b/src/lib/slide-toggle/slide-toggle-config.ts new file mode 100644 index 000000000000..b05a75b11db0 --- /dev/null +++ b/src/lib/slide-toggle/slide-toggle-config.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 {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('mat-slide-toggle-default-options', { + providedIn: 'root', + factory: () => ({disableToggleValue: false, disableDragValue: false}) + }); diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index 08a97ade0a72..15a5e56fbb4c 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -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; @@ -849,6 +940,8 @@ describe('MatSlideToggle with forms', () => { [tabIndex]="slideTabindex" [labelPosition]="labelPosition" [disableRipple]="disableRipple" + (toggleChange)="onSlideToggleChange()" + (dragChange)="onSlideDragChange()" (change)="onSlideChange($event)" (click)="onSlideClick($event)"> Test Slide Toggle @@ -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({ diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 776091cb4daa..8d67972f3a64 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -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 = new EventEmitter(); + /** + * 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 = new EventEmitter(); + + /** + * 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 = new EventEmitter(); + /** 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 `