@@ -503,6 +503,15 @@ export class MatSlider extends _MatSliderMixinBase
503
503
/** Used to subscribe to global move and end events */
504
504
protected _document : Document ;
505
505
506
+ /**
507
+ * Identifier used to attribute a touch event to a particular slider.
508
+ * Will be undefined if one of the following conditions is true:
509
+ * - The user isn't dragging using a touch device.
510
+ * - The browser doesn't support `Touch.identifier`.
511
+ * - Dragging hasn't started yet.
512
+ */
513
+ private _touchId : number | undefined ;
514
+
506
515
constructor ( elementRef : ElementRef ,
507
516
private _focusMonitor : FocusMonitor ,
508
517
private _changeDetectorRef : ChangeDetectorRef ,
@@ -636,21 +645,26 @@ export class MatSlider extends _MatSliderMixinBase
636
645
}
637
646
638
647
this . _ngZone . run ( ( ) => {
639
- const oldValue = this . value ;
640
- const pointerPosition = getPointerPositionOnPage ( event ) ;
641
- this . _isSliding = true ;
642
- this . _lastPointerEvent = event ;
643
- event . preventDefault ( ) ;
644
- this . _focusHostElement ( ) ;
645
- this . _onMouseenter ( ) ; // Simulate mouseenter in case this is a mobile device.
646
- this . _bindGlobalEvents ( event ) ;
647
- this . _focusHostElement ( ) ;
648
- this . _updateValueFromPosition ( pointerPosition ) ;
649
- this . _valueOnSlideStart = oldValue ;
650
-
651
- // Emit a change and input event if the value changed.
652
- if ( oldValue != this . value ) {
653
- this . _emitInputEvent ( ) ;
648
+ this . _touchId = isTouchEvent ( event ) ?
649
+ getTouchIdForSlider ( event , this . _elementRef . nativeElement ) : undefined ;
650
+ const pointerPosition = getPointerPositionOnPage ( event , this . _touchId ) ;
651
+
652
+ if ( pointerPosition ) {
653
+ const oldValue = this . value ;
654
+ this . _isSliding = true ;
655
+ this . _lastPointerEvent = event ;
656
+ event . preventDefault ( ) ;
657
+ this . _focusHostElement ( ) ;
658
+ this . _onMouseenter ( ) ; // Simulate mouseenter in case this is a mobile device.
659
+ this . _bindGlobalEvents ( event ) ;
660
+ this . _focusHostElement ( ) ;
661
+ this . _updateValueFromPosition ( pointerPosition ) ;
662
+ this . _valueOnSlideStart = oldValue ;
663
+
664
+ // Emit a change and input event if the value changed.
665
+ if ( oldValue != this . value ) {
666
+ this . _emitInputEvent ( ) ;
667
+ }
654
668
}
655
669
} ) ;
656
670
}
@@ -661,31 +675,41 @@ export class MatSlider extends _MatSliderMixinBase
661
675
*/
662
676
private _pointerMove = ( event : TouchEvent | MouseEvent ) => {
663
677
if ( this . _isSliding ) {
664
- // Prevent the slide from selecting anything else.
665
- event . preventDefault ( ) ;
666
- const oldValue = this . value ;
667
- this . _lastPointerEvent = event ;
668
- this . _updateValueFromPosition ( getPointerPositionOnPage ( event ) ) ;
669
-
670
- // Native range elements always emit `input` events when the value changed while sliding.
671
- if ( oldValue != this . value ) {
672
- this . _emitInputEvent ( ) ;
678
+ const pointerPosition = getPointerPositionOnPage ( event , this . _touchId ) ;
679
+
680
+ if ( pointerPosition ) {
681
+ // Prevent the slide from selecting anything else.
682
+ event . preventDefault ( ) ;
683
+ const oldValue = this . value ;
684
+ this . _lastPointerEvent = event ;
685
+ this . _updateValueFromPosition ( pointerPosition ) ;
686
+
687
+ // Native range elements always emit `input` events when the value changed while sliding.
688
+ if ( oldValue != this . value ) {
689
+ this . _emitInputEvent ( ) ;
690
+ }
673
691
}
674
692
}
675
693
}
676
694
677
695
/** Called when the user has lifted their pointer. Bound on the document level. */
678
696
private _pointerUp = ( event : TouchEvent | MouseEvent ) => {
679
697
if ( this . _isSliding ) {
680
- event . preventDefault ( ) ;
681
- this . _removeGlobalEvents ( ) ;
682
- this . _isSliding = false ;
683
-
684
- if ( this . _valueOnSlideStart != this . value && ! this . disabled ) {
685
- this . _emitChangeEvent ( ) ;
698
+ if ( ! isTouchEvent ( event ) || typeof this . _touchId !== 'number' ||
699
+ // Note that we use `changedTouches`, rather than `touches` because it
700
+ // seems like in most cases `touches` is empty for `touchend` events.
701
+ findMatchingTouch ( event . changedTouches , this . _touchId ) ) {
702
+ event . preventDefault ( ) ;
703
+ this . _removeGlobalEvents ( ) ;
704
+ this . _isSliding = false ;
705
+ this . _touchId = undefined ;
706
+
707
+ if ( this . _valueOnSlideStart != this . value && ! this . disabled ) {
708
+ this . _emitChangeEvent ( ) ;
709
+ }
710
+
711
+ this . _valueOnSlideStart = this . _lastPointerEvent = null ;
686
712
}
687
-
688
- this . _valueOnSlideStart = this . _lastPointerEvent = null ;
689
713
}
690
714
}
691
715
@@ -919,8 +943,47 @@ function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
919
943
}
920
944
921
945
/** Gets the coordinates of a touch or mouse event relative to the viewport. */
922
- function getPointerPositionOnPage ( event : MouseEvent | TouchEvent ) {
923
- // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
924
- const point = isTouchEvent ( event ) ? ( event . touches [ 0 ] || event . changedTouches [ 0 ] ) : event ;
925
- return { x : point . clientX , y : point . clientY } ;
946
+ function getPointerPositionOnPage ( event : MouseEvent | TouchEvent , id : number | undefined ) {
947
+ let point : { clientX : number , clientY : number } | undefined ;
948
+
949
+ if ( isTouchEvent ( event ) ) {
950
+ // The `identifier` could be undefined if the browser doesn't support `TouchEvent.identifier`.
951
+ // If that's the case, attribute the first touch to all active sliders. This should still cover
952
+ // the most common case while only breaking multi-touch.
953
+ if ( typeof id === 'number' ) {
954
+ point = findMatchingTouch ( event . touches , id ) || findMatchingTouch ( event . changedTouches , id ) ;
955
+ } else {
956
+ // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
957
+ point = event . touches [ 0 ] || event . changedTouches [ 0 ] ;
958
+ }
959
+ } else {
960
+ point = event ;
961
+ }
962
+
963
+ return point ? { x : point . clientX , y : point . clientY } : undefined ;
964
+ }
965
+
966
+ /** Finds a `Touch` with a specific ID in a `TouchList`. */
967
+ function findMatchingTouch ( touches : TouchList , id : number ) : Touch | undefined {
968
+ for ( let i = 0 ; i < touches . length ; i ++ ) {
969
+ if ( touches [ i ] . identifier === id ) {
970
+ return touches [ i ] ;
971
+ }
972
+ }
973
+
974
+ return undefined ;
975
+ }
976
+
977
+
978
+ /** Gets the unique ID of a touch that matches a specific slider. */
979
+ function getTouchIdForSlider ( event : TouchEvent , sliderHost : HTMLElement ) : number | undefined {
980
+ for ( let i = 0 ; i < event . touches . length ; i ++ ) {
981
+ const target = event . touches [ i ] . target as HTMLElement ;
982
+
983
+ if ( sliderHost === target || sliderHost . contains ( target ) ) {
984
+ return event . touches [ i ] . identifier ;
985
+ }
986
+ }
987
+
988
+ return undefined ;
926
989
}
0 commit comments