Skip to content

Commit 6999690

Browse files
committed
feat(slide-toggle): make slide-toggle click and drag actions configurable
1 parent 4ee0b87 commit 6999690

File tree

4 files changed

+149
-4
lines changed

4 files changed

+149
-4
lines changed

src/lib/slide-toggle/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88

99
export * from './slide-toggle-module';
1010
export * from './slide-toggle';
11-
11+
export * from './slide-toggle-config';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {InjectionToken} from '@angular/core';
9+
10+
11+
/**
12+
* Slide toggle click action when user click on input element.
13+
* noop: Do not toggle checked.
14+
* check: Only toggle checked status. Default behavior
15+
* undefined: Same as `check`.
16+
*/
17+
export type MatSlideToggleClickAction = 'noop' | 'check' | undefined;
18+
19+
/**
20+
* Injection token that can be used to specify the slide toggle click behavior.
21+
*/
22+
export const MAT_SLIDE_TOGGLE_CLICK_ACTION =
23+
new InjectionToken<MatSlideToggleClickAction>('mat-slide-toggle-click-action');
24+
25+
/**
26+
* Slide toggle drag action when user drag on slide toggle.
27+
* noop: Do not toggle checked.
28+
* check: Only toggle checked status. Default behavior
29+
* undefined: Same as `check`.
30+
*/
31+
export type MatSlideToggleDragAction = 'noop' | 'check' | undefined;
32+
33+
/**
34+
* Injection token that can be used to specify the slide toggle drag behavior.
35+
*/
36+
export const MAT_SLIDE_TOGGLE_DRAG_ACTION =
37+
new InjectionToken<MatSlideToggleDragAction>('mat-slide-toggle-drag-action');

src/lib/slide-toggle/slide-toggle.spec.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/f
66
import {defaultRippleAnimationConfig} from '@angular/material/core';
77
import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
88
import {TestGestureConfig} from '../slider/test-gesture-config';
9+
import {MAT_SLIDE_TOGGLE_CLICK_ACTION, MAT_SLIDE_TOGGLE_DRAG_ACTION} from './slide-toggle-config';
910
import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index';
1011

1112
describe('MatSlideToggle without forms', () => {
@@ -355,6 +356,103 @@ describe('MatSlideToggle without forms', () => {
355356
}));
356357
});
357358

359+
describe('custom action configuration', () => {
360+
it('should not change value on click when click action is noop', fakeAsync(() => {
361+
TestBed
362+
.resetTestingModule()
363+
.configureTestingModule({
364+
imports: [MatSlideToggleModule],
365+
declarations: [SlideToggleBasic],
366+
providers: [
367+
{
368+
provide: HAMMER_GESTURE_CONFIG,
369+
useFactory: () => gestureConfig = new TestGestureConfig()
370+
},
371+
{ provide: MAT_SLIDE_TOGGLE_CLICK_ACTION, useValue: 'noop'},
372+
{
373+
provide: MutationObserverFactory,
374+
useValue: {
375+
create: (callback: Function) => {
376+
mutationObserverCallbacks.push(callback);
377+
return {observe: () => {}, disconnect: () => {}};
378+
}
379+
}
380+
}
381+
]
382+
});
383+
const fixture = TestBed.createComponent(SlideToggleBasic);
384+
const slideToggleDebug = fixture.debugElement.query(By.css('mat-slide-toggle'));
385+
386+
const slideToggle = slideToggleDebug.componentInstance;
387+
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
388+
const labelElement = fixture.debugElement.query(By.css('label')).nativeElement;
389+
390+
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
391+
392+
labelElement.click();
393+
fixture.detectChanges();
394+
395+
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
396+
397+
inputElement.click();
398+
fixture.detectChanges();
399+
400+
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
401+
}));
402+
403+
it('should not change value on dragging when drag action is noop', fakeAsync(() => {
404+
TestBed
405+
.resetTestingModule()
406+
.configureTestingModule({
407+
imports: [MatSlideToggleModule],
408+
declarations: [SlideToggleBasic],
409+
providers: [
410+
{
411+
provide: HAMMER_GESTURE_CONFIG,
412+
useFactory: () => gestureConfig = new TestGestureConfig()
413+
},
414+
{ provide: MAT_SLIDE_TOGGLE_DRAG_ACTION, useValue: 'noop'},
415+
{
416+
provide: MutationObserverFactory,
417+
useValue: {
418+
create: (callback: Function) => {
419+
mutationObserverCallbacks.push(callback);
420+
return {observe: () => {}, disconnect: () => {}};
421+
}
422+
}
423+
}
424+
]
425+
});
426+
const fixture = TestBed.createComponent(SlideToggleBasic);
427+
const testComponent = fixture.debugElement.componentInstance;
428+
const slideToggleDebug = fixture.debugElement.query(By.css('mat-slide-toggle'));
429+
const thumbContainerDebug = slideToggleDebug
430+
.query(By.css('.mat-slide-toggle-thumb-container'));
431+
432+
const slideThumbContainer = thumbContainerDebug.nativeElement;
433+
const slideToggle = slideToggleDebug.componentInstance;
434+
435+
expect(slideToggle.checked).toBe(false);
436+
437+
gestureConfig.emitEventForElement('slidestart', slideThumbContainer);
438+
439+
expect(slideThumbContainer.classList).toContain('mat-dragging');
440+
441+
gestureConfig.emitEventForElement('slide', slideThumbContainer, {
442+
deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
443+
});
444+
445+
gestureConfig.emitEventForElement('slideend', slideThumbContainer);
446+
447+
// Flush the timeout for the slide ending.
448+
tick();
449+
450+
expect(slideToggle.checked).toBe(false, 'Expect slide toggle value not changed');
451+
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
452+
expect(testComponent.lastEvent).toBeUndefined();
453+
}));
454+
});
455+
358456
describe('with dragging', () => {
359457
let fixture: ComponentFixture<any>;
360458

src/lib/slide-toggle/slide-toggle.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ import {
4242
RippleRef,
4343
} from '@angular/material/core';
4444
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
45+
import {
46+
MAT_SLIDE_TOGGLE_CLICK_ACTION,
47+
MAT_SLIDE_TOGGLE_DRAG_ACTION,
48+
MatSlideToggleClickAction,
49+
MatSlideToggleDragAction
50+
} from './slide-toggle-config';
4551

4652
// Increasing integer for generating unique ids for slide-toggle components.
4753
let nextUniqueId = 0;
@@ -172,7 +178,11 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
172178
private _changeDetectorRef: ChangeDetectorRef,
173179
@Attribute('tabindex') tabIndex: string,
174180
private _ngZone: NgZone,
175-
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) {
181+
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string,
182+
@Optional() @Inject(MAT_SLIDE_TOGGLE_CLICK_ACTION)
183+
public _clickAction?: MatSlideToggleClickAction,
184+
@Optional() @Inject(MAT_SLIDE_TOGGLE_DRAG_ACTION)
185+
public _dragAction?: MatSlideToggleDragAction) {
176186

177187
super(elementRef);
178188
this.tabIndex = parseInt(tabIndex) || 0;
@@ -198,7 +208,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
198208
// Releasing the pointer over the `<label>` element while dragging triggers another
199209
// click event on the `<label>` element. This means that the checked state of the underlying
200210
// input changed unintentionally and needs to be changed back.
201-
if (this._dragging) {
211+
if (this._dragging || this._clickAction === 'noop') {
202212
this._inputElement.nativeElement.checked = this.checked;
203213
return;
204214
}
@@ -315,7 +325,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
315325
if (this._dragging) {
316326
const newCheckedValue = this._dragPercentage > 50;
317327

318-
if (newCheckedValue !== this.checked) {
328+
if (newCheckedValue !== this.checked && this._dragAction !== 'noop') {
319329
this.checked = newCheckedValue;
320330
this._emitChangeEvent();
321331
}

0 commit comments

Comments
 (0)