Skip to content

fix(slide-toggle): input not updated after drag #3067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/lib/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ describe('MdSlideToggle', () => {
let slideToggleElement: HTMLElement;
let slideToggleControl: NgControl;
let slideThumbContainer: HTMLElement;
let inputElement: HTMLInputElement;

beforeEach(async(() => {
fixture = TestBed.createComponent(SlideToggleTestApp);
Expand All @@ -453,6 +454,8 @@ describe('MdSlideToggle', () => {
slideToggleElement = slideToggleDebug.nativeElement;
slideToggleControl = slideToggleDebug.injector.get(NgControl);
slideThumbContainer = thumbContainerDebug.nativeElement;

inputElement = slideToggleElement.querySelector('input');
}));

it('should drag from start to end', fakeAsync(() => {
Expand Down Expand Up @@ -495,7 +498,7 @@ describe('MdSlideToggle', () => {
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
}));

it('should not drag when disbaled', fakeAsync(() => {
it('should not drag when disabled', fakeAsync(() => {
slideToggle.disabled = true;

expect(slideToggle.checked).toBe(false);
Expand Down Expand Up @@ -538,6 +541,28 @@ describe('MdSlideToggle', () => {
expect(testComponent.lastEvent.checked).toBe(true);
}));

it('should update the checked property of the input', fakeAsync(() => {
expect(inputElement.checked).toBe(false);

gestureConfig.emitEventForElement('slidestart', slideThumbContainer);

expect(slideThumbContainer.classList).toContain('mat-dragging');

gestureConfig.emitEventForElement('slide', slideThumbContainer, {
deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
});

gestureConfig.emitEventForElement('slideend', slideThumbContainer);
fixture.detectChanges();

expect(inputElement.checked).toBe(true);

// Flush the timeout for the slide ending.
tick();

expect(slideThumbContainer.classList).not.toContain('mat-dragging');
}));

});

describe('with a FormControl', () => {
Expand Down
75 changes: 41 additions & 34 deletions src/lib/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
event.stopPropagation();

// Once a drag is currently in progress, we do not want to toggle the slide-toggle on a click.
if (!this.disabled && !this._slideRenderer.isDragging()) {
if (!this.disabled && !this._slideRenderer.dragging) {
this.toggle();

// Emit our custom change event if the native input emitted one.
Expand Down Expand Up @@ -254,22 +254,20 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
}

_onDrag(event: HammerInput) {
if (this._slideRenderer.isDragging()) {
if (this._slideRenderer.dragging) {
this._slideRenderer.updateThumbPosition(event.deltaX);
}
}

_onDragEnd() {
if (!this._slideRenderer.isDragging()) {
return;
}

// Notice that we have to stop outside of the current event handler,
// because otherwise the click event will be fired and will reset the new checked variable.
setTimeout(() => {
this.checked = this._slideRenderer.stopThumbDrag();
if (this._slideRenderer.dragging) {
this.checked = this._slideRenderer.dragPercentage > 50;
this._emitChangeEvent();
}, 0);

// The drag should be stopped outside of the current event handler, because otherwise the
// click event will be fired before and will revert the drag change.
setTimeout(() => this._slideRenderer.stopThumbDrag());
}
}

}
Expand All @@ -279,56 +277,65 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor {
*/
class SlideToggleRenderer {

/** Reference to the thumb HTMLElement. */
private _thumbEl: HTMLElement;

/** Reference to the thumb bar HTMLElement. */
private _thumbBarEl: HTMLElement;

/** Width of the thumb bar of the slide-toggle. */
private _thumbBarWidth: number;
private _checked: boolean;
private _percentage: number;

/** Previous checked state before drag started. */
private _previousChecked: boolean;

/** Percentage of the thumb while dragging. */
dragPercentage: number;

/** Whether the thumb is currently being dragged. */
dragging: boolean = false;

constructor(private _elementRef: ElementRef) {
this._thumbEl = _elementRef.nativeElement.querySelector('.mat-slide-toggle-thumb-container');
this._thumbBarEl = _elementRef.nativeElement.querySelector('.mat-slide-toggle-bar');
}

/** Whether the slide-toggle is currently dragging. */
isDragging(): boolean {
return !!this._thumbBarWidth;
}


/** Initializes the drag of the slide-toggle. */
startThumbDrag(checked: boolean) {
if (!this.isDragging()) {
this._thumbBarWidth = this._thumbBarEl.clientWidth - this._thumbEl.clientWidth;
this._checked = checked;
this._thumbEl.classList.add('mat-dragging');
}
if (this.dragging) { return; }

this._thumbBarWidth = this._thumbBarEl.clientWidth - this._thumbEl.clientWidth;
this._thumbEl.classList.add('mat-dragging');

this._previousChecked = checked;
this.dragging = true;
}

/** Stops the current drag and returns the new checked value. */
/** Resets the current drag and returns the new checked value. */
stopThumbDrag(): boolean {
if (this.isDragging()) {
this._thumbBarWidth = null;
this._thumbEl.classList.remove('mat-dragging');
if (!this.dragging) { return; }

applyCssTransform(this._thumbEl, '');
this.dragging = false;
this._thumbEl.classList.remove('mat-dragging');

return this._percentage > 50;
}
// Reset the transform because the component will take care of the thumb position after drag.
applyCssTransform(this._thumbEl, '');

return this.dragPercentage > 50;
}

/** Updates the thumb containers position from the specified distance. */
updateThumbPosition(distance: number) {
this._percentage = this._getThumbPercentage(distance);
applyCssTransform(this._thumbEl, `translate3d(${this._percentage}%, 0, 0)`);
this.dragPercentage = this._getThumbPercentage(distance);
applyCssTransform(this._thumbEl, `translate3d(${this.dragPercentage}%, 0, 0)`);
}

/** Retrieves the percentage of thumb from the moved distance. */
private _getThumbPercentage(distance: number) {
let percentage = (distance / this._thumbBarWidth) * 100;

// When the toggle was initially checked, then we have to start the drag at the end.
if (this._checked) {
if (this._previousChecked) {
percentage += 100;
}

Expand Down