Skip to content

Commit 8a20fe7

Browse files
committed
feat(slide-toggle): add drag functionality to thumb
1 parent 7a1bb19 commit 8a20fe7

File tree

3 files changed

+86
-4
lines changed

3 files changed

+86
-4
lines changed

src/components/slide-toggle/slide-toggle.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<label class="md-slide-toggle-label">
2+
23
<div class="md-slide-toggle-container">
34
<div class="md-slide-toggle-bar"></div>
4-
<div class="md-slide-toggle-thumb-container">
5+
6+
<div class="md-slide-toggle-thumb-container" #thumbContainer>
57
<div class="md-slide-toggle-thumb">
68
<div class="md-ink-ripple"></div>
79
</div>

src/components/slide-toggle/slide-toggle.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ $md-slide-toggle-margin: 16px !default;
129129

130130
transition: $swift-linear;
131131
transition-property: transform;
132+
133+
// Once the thumb container is being dragged around, we remove the transition duration to
134+
// make the drag feeling fast and not delayed.
135+
&.md-dragging {
136+
transition-duration: 0ms;
137+
}
132138
}
133139

134140
// The thumb will be elevated from the slide-toggle bar.

src/components/slide-toggle/slide-toggle.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
Input,
88
Output,
99
EventEmitter,
10-
AfterContentInit
10+
AfterContentInit,
11+
ViewChild,
12+
AfterViewInit
1113
} from '@angular/core';
1214
import {
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

219293
export const MD_SLIDE_TOGGLE_DIRECTIVES = [MdSlideToggle];

0 commit comments

Comments
 (0)