Skip to content

Commit

Permalink
feat(slide-toggle): add drag functionality to thumb
Browse files Browse the repository at this point in the history
  • Loading branch information
devversion committed Jul 8, 2016
1 parent 3c74ae0 commit 975495a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 17 deletions.
8 changes: 7 additions & 1 deletion src/components/slide-toggle/slide-toggle.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<label class="md-slide-toggle-label">

<div class="md-slide-toggle-container">
<div class="md-slide-toggle-bar"></div>
<div class="md-slide-toggle-thumb-container">

<div class="md-slide-toggle-thumb-container"
(slidestart)="_onDragStart($event)"
(slide)="_onDrag($event)"
(slideend)="_onDragEnd($event)">

<div class="md-slide-toggle-thumb">
<div class="md-ink-ripple"></div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/components/slide-toggle/slide-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ $md-slide-toggle-margin: 16px !default;

transition: $swift-linear;
transition-property: transform;

// Once the thumb container is being dragged around, we remove the transition duration to
// make the drag feeling fast and not delayed.
&.md-dragging {
transition-duration: 0ms;
}
}

// The thumb will be elevated from the slide-toggle bar.
Expand Down
107 changes: 103 additions & 4 deletions src/components/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import {
Input,
Output,
EventEmitter,
AfterContentInit
AfterContentInit,
ViewChild
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR
} from '@angular/forms';
import { BooleanFieldValue } from '@angular2-material/core/annotations/field-value';
import { Observable } from 'rxjs/Observable';
import {BooleanFieldValue} from '@angular2-material/core/annotations/field-value';
import {Observable} from 'rxjs/Observable';
import {applyCssTransform} from '@angular2-material/core/style/apply-transform';

export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -58,6 +60,13 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
private _hasFocus: boolean = false;
private _isMousedown: boolean = false;
private _isInitialized: boolean = false;
private _domRenderer: MdSlideToggleRenderer = null;

// Drag pointer, which holds information about the current drag.
private _dragPointer: {
barWidth: number;
percentage?: number;
};

@Input() @BooleanFieldValue() disabled: boolean = false;
@Input() name: string = null;
Expand All @@ -74,6 +83,7 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {

constructor(private _elementRef: ElementRef,
private _renderer: Renderer) {
this._domRenderer = new MdSlideToggleRenderer(this._elementRef);
}

/** TODO: internal */
Expand All @@ -95,7 +105,8 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
// emit its event object to the component's `change` output.
event.stopPropagation();

if (!this.disabled) {
// Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
if (!this.disabled && !this._dragPointer) {
this.toggle();
}
}
Expand Down Expand Up @@ -202,13 +213,101 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
}
}

/** Emits the change event to the `change` output EventEmitter */
private _emitChangeEvent() {
let event = new MdSlideToggleChange();
event.source = this;
event.checked = this.checked;
this._change.emit(event);
}


/** @internal */
_onDragStart() {
if (this._dragPointer) {
return;
}

let thumbBarRect = this._domRenderer.getThumbBarClientRect();
let thumbRect = this._domRenderer.getThumbClientRect();

this._dragPointer = {
barWidth: thumbBarRect.width - thumbRect.width
};

this._domRenderer.toggleDragging(true);
}

/** @internal */
_onDrag(event: HammerInput) {
if (!this._dragPointer) {
return;
}

let barWidth = this._dragPointer.barWidth;
let distance = Math.max(-barWidth, Math.min(event.deltaX, barWidth));

let percentage = (distance / barWidth) * 100;

if (percentage < 0) {
percentage += 100;
}

this._domRenderer.updateThumbPosition(percentage);
this._dragPointer.percentage = percentage;
}

/** @internal */
_onDragEnd() {
if (!this._dragPointer) {
return;
}

this.checked = this._dragPointer.percentage > 50;

this._domRenderer.updateThumbPosition(null);
this._domRenderer.toggleDragging(false);

// We have to clear the drag after one tick, because otherwise
// the click event will fire and toggle the slide-toggle again.
setTimeout(() => { this._dragPointer = null; }, 0);
}

}

/**
* Renderer for the Slide Toggle component, which separates DOM modification in it's own class
*/
export class MdSlideToggleRenderer {

constructor(private _elementRef: ElementRef) {}

getThumbClientRect(): ClientRect {
let thumbEl = this._elementRef.nativeElement.querySelector('.md-slide-toggle-thumb-container');
return thumbEl.getBoundingClientRect();
}

getThumbBarClientRect(): ClientRect {
let thumbBarEl = this._elementRef.nativeElement.querySelector('.md-slide-toggle-bar');
return thumbBarEl.getBoundingClientRect();
}

/**
* Updates the thumb containers position by using the specified percentage.
* When the percentage is set to `null`, the custom thumb position will be removed.
*/
updateThumbPosition(percentage: number) {
let thumbEl = this._elementRef.nativeElement.querySelector('.md-slide-toggle-thumb-container');
applyCssTransform(thumbEl, percentage === null ? '' : `translate3d(${percentage}%, 0, 0)`);
}

/** Toggles the dragging class for the thumb container to toggle the transition duration. */
toggleDragging(isDragging: boolean) {
let thumbEl = this._elementRef.nativeElement.querySelector('.md-slide-toggle-thumb-container');
thumbEl.classList.toggle('md-dragging', isDragging);
}


}

export const MD_SLIDE_TOGGLE_DIRECTIVES = [MdSlideToggle];
40 changes: 28 additions & 12 deletions src/core/gestures/MdGestureConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {HammerGestureConfig} from '@angular/platform-browser';
/* Adjusts configuration of our gesture library, Hammer. */
@Injectable()
export class MdGestureConfig extends HammerGestureConfig {

/* List of new event names to add to the gesture support list */
events: string[] = [
'drag',
Expand All @@ -12,6 +13,11 @@ export class MdGestureConfig extends HammerGestureConfig {
'dragright',
'dragleft',
'longpress',
'slide',
'slidestart',
'slideend',
'slideright',
'slideleft'
];

/*
Expand All @@ -29,22 +35,32 @@ export class MdGestureConfig extends HammerGestureConfig {
buildHammer(element: HTMLElement) {
var mc = new Hammer(element);

// create custom gesture recognizers
var drag = new Hammer.Pan({event: 'drag', threshold: 6});
var longpress = new Hammer.Press({event: 'longpress', time: 500});
// Create custom gesture recognizers
let drag = this._createRecognizer(Hammer.Pan, {event: 'drag', threshold: 6}, Hammer.Swipe);
let slide = this._createRecognizer(Hammer.Pan, {event: 'slide', threshold: 0}, Hammer.Swipe);
let longpress = this._createRecognizer(Hammer.Press, {event: 'longpress', time: 500});

let pan = new Hammer.Pan();
let swipe = new Hammer.Swipe();

// ensure custom recognizers can coexist with the default gestures (i.e. pan, press, swipe)
var pan = new Hammer.Pan();
var press = new Hammer.Press();
var swipe = new Hammer.Swipe();
drag.recognizeWith(pan);
drag.recognizeWith(swipe);
// Overwrite the default `pan` event to use the swipe event.
pan.recognizeWith(swipe);
longpress.recognizeWith(press);

// add customized gestures to Hammer manager
mc.add([drag, pan, swipe, press, longpress]);
// Add customized gestures to Hammer manager
mc.add([drag, slide, pan, longpress]);

return mc;
}

/** Creates a new recognizer, without affecting the default recognizers of HammerJS */
private _createRecognizer(type: RecognizerStatic, options: any, ...extra: RecognizerStatic[]) {
let recognizer = new type(options);

// Add the default recognizer to the new custom recognizer.
extra.push(type);
extra.forEach(entry => recognizer.recognizeWith(new entry()));

return recognizer;
}

}
1 change: 1 addition & 0 deletions test/karma.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function config(config) {
],
files: [
{pattern: 'dist/vendor/core-js/client/core.js', included: true, watched: false},
{pattern: 'dist/vendor/hammerjs/hammer.min.js', included: true, watched: false},
{pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false},
{pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false},
{pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false},
Expand Down

0 comments on commit 975495a

Please sign in to comment.