Skip to content

Commit

Permalink
fix(slide-toggle): drag not working in edge
Browse files Browse the repository at this point in the history
Some browsers like Edge or IE11 emit `click` events differently than other browsers like Chrome. For example does Edge still emit a `click` event if the pointer drags something around and is being released over the `<label>` element. This doesn't happen on Chrome.

Right now this means that the new checked state from the drag is being reverted by the `click` event that then causes a `change` event on the underlying input element. To ensure that the behavior is the same across all supported browsers, the checked state from the `click` event is being ignored while dragging.

Fixes angular#8391
  • Loading branch information
devversion committed Nov 13, 2017
1 parent 8dfe470 commit 983d0b4
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 17 deletions.
23 changes: 23 additions & 0 deletions src/lib/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,29 @@ describe('MatSlideToggle without forms', () => {
.toBeFalsy('Expected the slide-toggle to not emit a change event.');
}));

it('should ignore clicks on the label element while dragging', fakeAsync(() => {
expect(slideToggle.checked).toBe(false);

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

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

// Fake a change event that has been fired after dragging through the click on pointer
// release (noticeable on IE11, Edge)
inputElement.checked = false;
dispatchFakeEvent(inputElement, 'change');

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

expect(slideThumbContainer.classList).not.toContain('mat-dragging');
expect(slideToggle.checked).toBe(true);
}));

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

Expand Down
33 changes: 16 additions & 17 deletions src/lib/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,32 +161,31 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
}

/**
* This function will called if the underlying input changed its value through user interaction.
*/
/** Method being called whenever the underlying input emits a change event. */
_onChangeEvent(event: Event) {
// We always have to stop propagation on the change event.
// Otherwise the change event, from the input element, will bubble up and
// emit its event object to the component's `change` output.
event.stopPropagation();

// Sync the value from the underlying input element with the slide-toggle component.
// Releasing the pointer over the `<label>` element while dragging triggers another
// click event on the `<label>` element. This means that the checked state of the underlying
// input changed unintentionally and needs to be changed back.
if (this._slideRenderer.dragging) {
this._inputElement.nativeElement.checked = this.checked;
return;
}

// Sync the value from the underlying input element with the component instance.
this.checked = this._inputElement.nativeElement.checked;

// Emit our custom change event if the native input emitted one.
// It is important to only emit it, if the native input triggered one, because we don't want
// to trigger a change event, when the `checked` variable changes programmatically.
// Emit our custom change event only if the underlying input emitted one. This ensures that
// there is no change event, when the checked state changes programmatically.
this._emitChangeEvent();
}

/** Method being called whenever the slide-toggle has been clicked. */
_onInputClick(event: Event) {
// In some situations the user will release the mouse on the label element. The label element
// redirects the click to the underlying input element and will result in a value change.
// Prevent the default behavior if dragging, because the value will be set after drag.
if (this._slideRenderer.dragging) {
event.preventDefault();
}

// We have to stop propagation for click events on the visual hidden input element.
// By default, when a user clicks on a label element, a generated click event will be
// dispatched on the associated input element. Since we are using a label element as our
Expand Down Expand Up @@ -269,10 +268,10 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro

_onDragEnd() {
if (this._slideRenderer.dragging) {
let _previousChecked = this.checked;
this.checked = this._slideRenderer.dragPercentage > 50;
const newCheckedValue = this._slideRenderer.dragPercentage > 50;

if (_previousChecked !== this.checked) {
if (newCheckedValue !== this.checked) {
this.checked = newCheckedValue;
this._emitChangeEvent();
}

Expand Down

0 comments on commit 983d0b4

Please sign in to comment.