Skip to content

Commit d6f287e

Browse files
devversionjelbourn
authored andcommitted
fix(slide-toggle): drag not working in edge (#8421)
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 #8391
1 parent 0f83b20 commit d6f287e

File tree

2 files changed

+39
-17
lines changed

2 files changed

+39
-17
lines changed

src/lib/slide-toggle/slide-toggle.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,29 @@ describe('MatSlideToggle without forms', () => {
475475
.toBeFalsy('Expected the slide-toggle to not emit a change event.');
476476
}));
477477

478+
it('should ignore clicks on the label element while dragging', fakeAsync(() => {
479+
expect(slideToggle.checked).toBe(false);
480+
481+
gestureConfig.emitEventForElement('slidestart', slideThumbContainer);
482+
gestureConfig.emitEventForElement('slide', slideThumbContainer, {
483+
deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
484+
});
485+
gestureConfig.emitEventForElement('slideend', slideThumbContainer);
486+
487+
expect(slideToggle.checked).toBe(true);
488+
489+
// Fake a change event that has been fired after dragging through the click on pointer
490+
// release (noticeable on IE11, Edge)
491+
inputElement.checked = false;
492+
dispatchFakeEvent(inputElement, 'change');
493+
494+
// Flush the timeout for the slide ending.
495+
tick();
496+
497+
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
498+
expect(slideToggle.checked).toBe(true);
499+
}));
500+
478501
it('should update the checked property of the input', fakeAsync(() => {
479502
expect(inputElement.checked).toBe(false);
480503

src/lib/slide-toggle/slide-toggle.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -161,32 +161,31 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
161161
this._focusMonitor.stopMonitoring(this._inputElement.nativeElement);
162162
}
163163

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

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

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

187+
/** Method being called whenever the slide-toggle has been clicked. */
182188
_onInputClick(event: Event) {
183-
// In some situations the user will release the mouse on the label element. The label element
184-
// redirects the click to the underlying input element and will result in a value change.
185-
// Prevent the default behavior if dragging, because the value will be set after drag.
186-
if (this._slideRenderer.dragging) {
187-
event.preventDefault();
188-
}
189-
190189
// We have to stop propagation for click events on the visual hidden input element.
191190
// By default, when a user clicks on a label element, a generated click event will be
192191
// dispatched on the associated input element. Since we are using a label element as our
@@ -269,10 +268,10 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
269268

270269
_onDragEnd() {
271270
if (this._slideRenderer.dragging) {
272-
let _previousChecked = this.checked;
273-
this.checked = this._slideRenderer.dragPercentage > 50;
271+
const newCheckedValue = this._slideRenderer.dragPercentage > 50;
274272

275-
if (_previousChecked !== this.checked) {
273+
if (newCheckedValue !== this.checked) {
274+
this.checked = newCheckedValue;
276275
this._emitChangeEvent();
277276
}
278277

0 commit comments

Comments
 (0)