Skip to content

fix(material-experimental/mdc-slider): sync ui on pointerdown #22579

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
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
19 changes: 14 additions & 5 deletions src/material-experimental/mdc-slider/slider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ describe('MDC-based MatSlider' , () => {
setValueByClick(sliderInstance, 0, platform.IOS);
expect(document.activeElement).toBe(inputInstance._hostElement);
});

it('should not break on when the page layout changes', () => {
sliderInstance._elementRef.nativeElement.style.marginLeft = '100px';
setValueByClick(sliderInstance, 10, platform.IOS);
expect(inputInstance.value).toBe(10);
sliderInstance._elementRef.nativeElement.style.marginLeft = 'initial';
});
});

describe('standard range slider', () => {
Expand Down Expand Up @@ -1891,6 +1898,7 @@ function setValueByClick(slider: MatSlider, value: number, isIOS: boolean) {
const sliderElement = slider._elementRef.nativeElement;
const {x, y} = getCoordsForValue(slider, value);

dispatchPointerEvent(sliderElement, 'mouseenter', x, y);
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, x, y, isIOS);
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, x, y, isIOS);
}
Expand All @@ -1901,6 +1909,7 @@ function slideToValue(slider: MatSlider, value: number, thumbPosition: Thumb, is
const {x: startX, y: startY} = getCoordsForValue(slider, slider._getInput(thumbPosition).value);
const {x: endX, y: endY} = getCoordsForValue(slider, value);

dispatchPointerEvent(sliderElement, 'mouseenter', startX, startY);
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, startX, startY, isIOS);
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_MOVE, endX, endY, isIOS);
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, endX, endY, isIOS);
Expand All @@ -1921,11 +1930,11 @@ function getCoordsForValue(slider: MatSlider, value: number): Point {
/** Dispatch a pointerdown or pointerup event if supported, otherwise dispatch the touch event. */
function dispatchPointerOrTouchEvent(
node: Node, type: PointerEventType, x: number, y: number, isIOS: boolean) {
if (isIOS) {
dispatchTouchEvent(node, pointerEventTypeToTouchEventType(type), x, y, x, y);
} else {
dispatchPointerEvent(node, type, x, y);
}
if (isIOS) {
dispatchTouchEvent(node, pointerEventTypeToTouchEventType(type), x, y, x, y);
} else {
dispatchPointerEvent(node, type, x, y);
}
}

/** Returns the touch event equivalent of the given pointer event. */
Expand Down
46 changes: 46 additions & 0 deletions src/material-experimental/mdc-slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,16 @@ export class MatSlider extends _MatSliderMixinBase
/** The display value of the end thumb. */
_endValueIndicatorText: string;

/**
* Whether the browser supports pointer events.
*
* We exclude iOS to mirror the MDC Foundation. The MDC Foundation cannot use pointer events on
* iOS because of this open bug - https://bugs.webkit.org/show_bug.cgi?id=220196.
*/
private _SUPPORTS_POINTER_EVENTS = typeof PointerEvent !== 'undefined'
&& !!PointerEvent
&& !this._platform.IOS;

/** Subscription to changes to the directionality (LTR / RTL) context for the application. */
private _dirChangeSubscription: Subscription;

Expand All @@ -643,6 +653,7 @@ export class MatSlider extends _MatSliderMixinBase
this._document = document;
this._window = this._document.defaultView || window;
this._dirChangeSubscription = this._dir.change.subscribe(() => this._onDirChange());
this._attachUISyncEventListener();
}

ngAfterViewInit() {
Expand Down Expand Up @@ -676,13 +687,48 @@ export class MatSlider extends _MatSliderMixinBase
this._foundation.destroy();
}
this._dirChangeSubscription.unsubscribe();
this._removeUISyncEventListener();
}

/** Returns true if the language direction for this slider element is right to left. */
_isRTL() {
return this._dir && this._dir.value === 'rtl';
}

/**
* Attaches an event listener that keeps sync the slider UI and the foundation in sync.
*
* Because the MDC Foundation stores the value of the bounding client rect when layout is called,
* we need to keep calling layout to avoid the position of the slider getting out of sync with
* what the foundation has stored. If we don't do this, the foundation will not be able to
* correctly calculate the slider value on click/slide.
*/
_attachUISyncEventListener(): void {
// Implementation detail: It may seem weird that we are using "mouseenter" instead of
// "mousedown" as the default for when a browser does not support pointer events. While we
// would prefer to use "mousedown" as the default, for some reason it does not work (the
// callback is never triggered).
if (this._SUPPORTS_POINTER_EVENTS) {
this._elementRef.nativeElement.addEventListener('pointerdown', this._layout);
} else {
this._elementRef.nativeElement.addEventListener('mouseenter', this._layout);
this._elementRef.nativeElement.addEventListener('touchstart', this._layout);
}
}

/** Removes the event listener that keeps sync the slider UI and the foundation in sync. */
_removeUISyncEventListener(): void {
if (this._SUPPORTS_POINTER_EVENTS) {
this._elementRef.nativeElement.removeEventListener('pointerdown', this._layout);
} else {
this._elementRef.nativeElement.removeEventListener('mouseenter', this._layout);
this._elementRef.nativeElement.removeEventListener('touchstart', this._layout);
}
}

/** Wrapper function for calling layout (needed for adding & removing an event listener). */
private _layout = () => this._foundation.layout();

/**
* Reinitializes the slider foundation and input state(s).
*
Expand Down