Skip to content

Commit 3b3c2ca

Browse files
crisbetojelbourn
authored andcommitted
fix(slider): stop dragging if page loses focus (#17849)
If the page loses focus (e.g. from an `alert` or the window being minimized) while the user is dragging a slider, it'll be stuck as dragging until the user comes back and clicks somewhere which might look weird and change the value. These changes stop the dragging sequence once the page is blurred.
1 parent 2e6045c commit 3b3c2ca

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/material/slider/slider.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,27 @@ describe('MatSlider', () => {
139139
expect(sliderNativeElement.classList).not.toContain('mat-slider-sliding');
140140
});
141141

142+
it('should stop dragging if the page loses focus', () => {
143+
const classlist = sliderNativeElement.classList;
144+
145+
expect(classlist).not.toContain('mat-slider-sliding');
146+
147+
dispatchSlideStartEvent(sliderNativeElement, 0);
148+
fixture.detectChanges();
149+
150+
expect(classlist).toContain('mat-slider-sliding');
151+
152+
dispatchSlideEvent(sliderNativeElement, 0.34);
153+
fixture.detectChanges();
154+
155+
expect(classlist).toContain('mat-slider-sliding');
156+
157+
dispatchFakeEvent(window, 'blur');
158+
fixture.detectChanges();
159+
160+
expect(classlist).not.toContain('mat-slider-sliding');
161+
});
162+
142163
it('should reset active state upon blur', () => {
143164
sliderInstance._isActive = true;
144165

src/material/slider/slider.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ export class MatSlider extends _MatSliderMixinBase
475475
return (this._dir && this._dir.value == 'rtl') ? 'rtl' : 'ltr';
476476
}
477477

478+
/** Keeps track of the last pointer event that was captured by the slider. */
479+
private _lastPointerEvent: MouseEvent | TouchEvent | null;
480+
478481
constructor(elementRef: ElementRef,
479482
private _focusMonitor: FocusMonitor,
480483
private _changeDetectorRef: ChangeDetectorRef,
@@ -513,6 +516,7 @@ export class MatSlider extends _MatSliderMixinBase
513516
const element = this._elementRef.nativeElement;
514517
element.removeEventListener('mousedown', this._pointerDown, activeEventOptions);
515518
element.removeEventListener('touchstart', this._pointerDown, activeEventOptions);
519+
this._lastPointerEvent = null;
516520
this._removeGlobalEvents();
517521
this._focusMonitor.stopMonitoring(this._elementRef);
518522
this._dirChangeSubscription.unsubscribe();
@@ -611,6 +615,7 @@ export class MatSlider extends _MatSliderMixinBase
611615
const oldValue = this.value;
612616
const pointerPosition = getPointerPositionOnPage(event);
613617
this._isSliding = true;
618+
this._lastPointerEvent = event;
614619
event.preventDefault();
615620
this._focusHostElement();
616621
this._onMouseenter(); // Simulate mouseenter in case this is a mobile device.
@@ -637,6 +642,7 @@ export class MatSlider extends _MatSliderMixinBase
637642
// Prevent the slide from selecting anything else.
638643
event.preventDefault();
639644
const oldValue = this.value;
645+
this._lastPointerEvent = event;
640646
this._updateValueFromPosition(getPointerPositionOnPage(event));
641647

642648
// Native range elements always emit `input` events when the value changed while sliding.
@@ -654,7 +660,7 @@ export class MatSlider extends _MatSliderMixinBase
654660

655661
event.preventDefault();
656662
this._removeGlobalEvents();
657-
this._valueOnSlideStart = this._pointerPositionOnStart = null;
663+
this._valueOnSlideStart = this._pointerPositionOnStart = this._lastPointerEvent = null;
658664
this._isSliding = false;
659665

660666
if (this._valueOnSlideStart != this.value && !this.disabled &&
@@ -665,6 +671,15 @@ export class MatSlider extends _MatSliderMixinBase
665671
}
666672
}
667673

674+
/** Called when the window has lost focus. */
675+
private _windowBlur = () => {
676+
// If the window is blurred while dragging we need to stop dragging because the
677+
// browser won't dispatch the `mouseup` and `touchend` events anymore.
678+
if (this._lastPointerEvent) {
679+
this._pointerUp(this._lastPointerEvent);
680+
}
681+
}
682+
668683
/**
669684
* Binds our global move and end events. They're bound at the document level and only while
670685
* dragging so that the user doesn't have to keep their pointer exactly over the slider
@@ -683,6 +698,9 @@ export class MatSlider extends _MatSliderMixinBase
683698
body.addEventListener('touchcancel', this._pointerUp, activeEventOptions);
684699
}
685700
}
701+
if (typeof window !== 'undefined' && window) {
702+
window.addEventListener('blur', this._windowBlur);
703+
}
686704
}
687705

688706
/** Removes any global event listeners that we may have added. */
@@ -695,6 +713,9 @@ export class MatSlider extends _MatSliderMixinBase
695713
body.removeEventListener('touchend', this._pointerUp, activeEventOptions);
696714
body.removeEventListener('touchcancel', this._pointerUp, activeEventOptions);
697715
}
716+
if (typeof window !== 'undefined' && window) {
717+
window.removeEventListener('blur', this._windowBlur);
718+
}
698719
}
699720

700721
/** Increments the slider by the given number of steps (negative number decrements). */

0 commit comments

Comments
 (0)