@@ -13,8 +13,9 @@ import {
13
13
ControlValueAccessor ,
14
14
NG_VALUE_ACCESSOR
15
15
} from '@angular/forms' ;
16
- import { BooleanFieldValue } from '@angular2-material/core/annotations/field-value' ;
17
- import { Observable } from 'rxjs/Observable' ;
16
+ import { BooleanFieldValue } from '@angular2-material/core/annotations/field-value' ;
17
+ import { Observable } from 'rxjs/Observable' ;
18
+ import { applyCssTransform } from '@angular2-material/core/style/apply-transform' ;
18
19
19
20
export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR : any = {
20
21
provide : NG_VALUE_ACCESSOR ,
@@ -58,6 +59,7 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
58
59
private _hasFocus : boolean = false ;
59
60
private _isMousedown : boolean = false ;
60
61
private _isInitialized : boolean = false ;
62
+ private _slideRenderer : SlideToggleRenderer = null ;
61
63
62
64
@Input ( ) @BooleanFieldValue ( ) disabled : boolean = false ;
63
65
@Input ( ) name : string = null ;
@@ -72,12 +74,12 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
72
74
// Returns the unique id for the visual hidden input.
73
75
getInputId = ( ) => `${ this . id || this . _uniqueId } -input` ;
74
76
75
- constructor ( private _elementRef : ElementRef ,
76
- private _renderer : Renderer ) {
77
- }
77
+ constructor ( private _elementRef : ElementRef , private _renderer : Renderer ) { }
78
78
79
79
/** TODO: internal */
80
80
ngAfterContentInit ( ) {
81
+ this . _slideRenderer = new SlideToggleRenderer ( this . _elementRef ) ;
82
+
81
83
// Mark this component as initialized in AfterContentInit because the initial checked value can
82
84
// possibly be set by NgModel or the checked attribute. This would cause the change event to
83
85
// be emitted, before the component is actually initialized.
@@ -95,7 +97,8 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
95
97
// emit its event object to the component's `change` output.
96
98
event . stopPropagation ( ) ;
97
99
98
- if ( ! this . disabled ) {
100
+ // Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
101
+ if ( ! this . disabled && ! this . _slideRenderer . isDragging ( ) ) {
99
102
this . toggle ( ) ;
100
103
}
101
104
}
@@ -202,13 +205,98 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
202
205
}
203
206
}
204
207
208
+ /** Emits the change event to the `change` output EventEmitter */
205
209
private _emitChangeEvent ( ) {
206
210
let event = new MdSlideToggleChange ( ) ;
207
211
event . source = this ;
208
212
event . checked = this . checked ;
209
213
this . _change . emit ( event ) ;
210
214
}
211
215
216
+
217
+ /** TODO: internal */
218
+ _onDragStart ( ) {
219
+ this . _slideRenderer . startThumbDrag ( this . checked ) ;
220
+ }
221
+
222
+ /** TODO: internal */
223
+ _onDrag ( event : HammerInput ) {
224
+ this . _slideRenderer . updateThumbPosition ( event . deltaX ) ;
225
+ }
226
+
227
+ /** TODO: internal */
228
+ _onDragEnd ( ) {
229
+ // Notice that we have to stop outside of the current event handler,
230
+ // because otherwise the click event will be fired and will reset the new checked variable.
231
+ setTimeout ( ( ) => {
232
+ this . checked = this . _slideRenderer . stopThumbDrag ( ) ;
233
+ } , 0 ) ;
234
+ }
235
+
236
+ }
237
+
238
+ /**
239
+ * Renderer for the Slide Toggle component, which separates DOM modification in its own class
240
+ */
241
+ class SlideToggleRenderer {
242
+
243
+ private _thumbEl : HTMLElement ;
244
+ private _thumbBarEl : HTMLElement ;
245
+ private _thumbBarWidth : number ;
246
+ private _checked : boolean ;
247
+ private _percentage : number ;
248
+
249
+ constructor ( private _elementRef : ElementRef ) {
250
+ this . _thumbEl = _elementRef . nativeElement . querySelector ( '.md-slide-toggle-thumb-container' ) ;
251
+ this . _thumbBarEl = _elementRef . nativeElement . querySelector ( '.md-slide-toggle-bar' ) ;
252
+ }
253
+
254
+ /** Whether the slide-toggle is currently dragging. */
255
+ isDragging ( ) : boolean {
256
+ return ! ! this . _thumbBarWidth ;
257
+ }
258
+
259
+ /** Initializes the drag of the slide-toggle. */
260
+ startThumbDrag ( checked : boolean ) {
261
+ if ( ! this . _thumbBarWidth ) {
262
+ this . _thumbBarWidth = this . _thumbBarEl . clientWidth - this . _thumbEl . clientWidth ;
263
+ this . _checked = checked ;
264
+ this . _thumbEl . classList . add ( 'md-dragging' ) ;
265
+ }
266
+ }
267
+
268
+ /** Stops the current drag and returns the new checked value. */
269
+ stopThumbDrag ( ) : boolean {
270
+ if ( this . _thumbBarWidth ) {
271
+ this . _thumbBarWidth = null ;
272
+ this . _thumbEl . classList . remove ( 'md-dragging' ) ;
273
+
274
+ applyCssTransform ( this . _thumbEl , '' ) ;
275
+
276
+ return this . _percentage > 50 ;
277
+ }
278
+ }
279
+
280
+ /** Updates the thumb containers position from the specified distance. */
281
+ updateThumbPosition ( distance : number ) {
282
+ if ( this . _thumbBarWidth ) {
283
+ this . _percentage = this . _getThumbPercentage ( distance ) ;
284
+ applyCssTransform ( this . _thumbEl , `translate3d(${ this . _percentage } %, 0, 0)` ) ;
285
+ }
286
+ }
287
+
288
+ /** Retrieves the percentage of thumb from the moved distance. */
289
+ private _getThumbPercentage ( distance : number ) {
290
+ let percentage = ( distance / this . _thumbBarWidth ) * 100 ;
291
+
292
+ // When the toggle was initially checked, then we have to start the drag at the end.
293
+ if ( this . _checked ) {
294
+ percentage += 100 ;
295
+ }
296
+
297
+ return Math . max ( 0 , Math . min ( percentage , 100 ) ) ;
298
+ }
299
+
212
300
}
213
301
214
302
export const MD_SLIDE_TOGGLE_DIRECTIVES = [ MdSlideToggle ] ;
0 commit comments