77 Input ,
88 Output ,
99 EventEmitter ,
10- AfterContentInit
10+ AfterContentInit ,
11+ ViewChild ,
12+ AfterViewInit
1113} from '@angular/core' ;
1214import {
1315 ControlValueAccessor ,
@@ -46,7 +48,7 @@ let nextId = 0;
4648 providers : [ MD_SLIDE_TOGGLE_VALUE_ACCESSOR ] ,
4749 changeDetection : ChangeDetectionStrategy . OnPush
4850} )
49- export class MdSlideToggle implements AfterContentInit , ControlValueAccessor {
51+ export class MdSlideToggle implements AfterContentInit , AfterViewInit , ControlValueAccessor {
5052
5153 private onChange = ( _ : any ) => { } ;
5254 private onTouched = ( ) => { } ;
@@ -59,6 +61,12 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
5961 private _isMousedown : boolean = false ;
6062 private _isInitialized : boolean = false ;
6163
64+ // Thumb Container Element, which is required for the dragging functionality.
65+ @ViewChild ( 'thumbContainer' ) private _thumbContainerRef : ElementRef ;
66+
67+ // Pointer Object for the dragging functionality of the thumb.
68+ private _dragPointer : any ;
69+
6270 @Input ( ) @BooleanFieldValue ( ) disabled : boolean = false ;
6371 @Input ( ) name : string = null ;
6472 @Input ( ) id : string = this . _uniqueId ;
@@ -84,6 +92,23 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
8492 this . _isInitialized = true ;
8593 }
8694
95+ /** TODO: internal */
96+ ngAfterViewInit ( ) {
97+ let hammerManager = new Hammer ( this . _thumbContainerRef . nativeElement ) ;
98+
99+ // The Pan / Drag recognition should immediately detect the starting drag event, without
100+ // waiting for the drag to exceed the elements width.
101+ hammerManager . add ( new Hammer . Pan ( {
102+ threshold : 0 ,
103+ pointers : 0 ,
104+ direction : Hammer . DIRECTION_HORIZONTAL
105+ } ) ) ;
106+
107+ hammerManager . on ( 'panstart' , this . _onDragStart . bind ( this ) ) ;
108+ hammerManager . on ( 'panmove' , this . _onDrag . bind ( this ) ) ;
109+ hammerManager . on ( 'panend' , this . _onDragEnd . bind ( this ) ) ;
110+ }
111+
87112 /**
88113 * The onChangeEvent method will be also called on click.
89114 * This is because everything for the slide-toggle is wrapped inside of a label,
@@ -96,7 +121,8 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
96121 // emit its event object to the component's `change` output.
97122 event . stopPropagation ( ) ;
98123
99- if ( ! this . disabled ) {
124+ // Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
125+ if ( ! this . disabled && ! this . _dragPointer ) {
100126 this . toggle ( ) ;
101127 }
102128 }
@@ -214,6 +240,54 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
214240 this . _change . emit ( event ) ;
215241 }
216242
243+
244+ private _onDragStart ( ) {
245+ if ( this . _dragPointer ) {
246+ return ;
247+ }
248+
249+ let thumbBarEl = this . _elementRef . nativeElement . querySelector ( '.md-slide-toggle-bar' ) ;
250+
251+ this . _dragPointer = {
252+ barWidth : thumbBarEl . getBoundingClientRect ( ) . width
253+ } ;
254+
255+ // Mark the thumb container with the `md-dragging` class to remove the duration of
256+ // transform transition.
257+ this . _thumbContainerRef . nativeElement . classList . add ( 'md-dragging' ) ;
258+ }
259+
260+ private _onDrag ( event : HammerInput ) {
261+ if ( ! this . _dragPointer ) {
262+ return ;
263+ }
264+
265+ let percentage = ( event . deltaX / this . _dragPointer . barWidth ) * 100 ;
266+
267+ // When the `slide-toggle` is currently checked, we invert the percentage, because it
268+ // starts from the right.
269+ percentage = this . checked ? 100 + percentage : percentage ;
270+ percentage = Math . max ( 0 , Math . min ( percentage , 100 ) ) ;
271+
272+ this . _dragPointer . percentage = percentage ;
273+ this . _thumbContainerRef . nativeElement . style . transform = `translate3d(${ percentage } %, 0, 0)` ;
274+ }
275+
276+ private _onDragEnd ( ) {
277+ if ( ! this . _dragPointer ) {
278+ return ;
279+ }
280+
281+ this . checked = this . _dragPointer . percentage > 50 ;
282+
283+ this . _thumbContainerRef . nativeElement . style . transform = '' ;
284+ this . _thumbContainerRef . nativeElement . classList . remove ( 'md-dragging' ) ;
285+
286+ // We have to clear the drag after one tick, because otherwise
287+ // the click event will fire and toggle the slide-toggle again.
288+ setTimeout ( ( ) => { this . _dragPointer = null ; } , 1 ) ;
289+ }
290+
217291}
218292
219293export const MD_SLIDE_TOGGLE_DIRECTIVES = [ MdSlideToggle ] ;
0 commit comments