@@ -54,6 +54,7 @@ import {
54
54
import { SpecificEventListener , EventType } from '@material/base' ;
55
55
import { MDCSliderAdapter , MDCSliderFoundation , Thumb , TickMark } from '@material/slider' ;
56
56
import { Subscription } from 'rxjs' ;
57
+ import { GlobalChangeAndInputListener } from './global-change-and-input-listener' ;
57
58
58
59
/** Represents a drag event emitted by the MatSlider component. */
59
60
export interface MatSliderDragEvent {
@@ -320,6 +321,12 @@ export class MatSliderThumb implements AfterViewInit, ControlValueAccessor, OnIn
320
321
/** Event emitted every time the MatSliderThumb is focused. */
321
322
@Output ( ) readonly _focus : EventEmitter < void > = new EventEmitter < void > ( ) ;
322
323
324
+ /** Event emitted on pointer up or after left or right arrow key presses. */
325
+ @Output ( ) readonly change : EventEmitter < Event > = new EventEmitter < Event > ( ) ;
326
+
327
+ /** Event emitted on each value change that happens to the slider. */
328
+ @Output ( ) readonly input : EventEmitter < Event > = new EventEmitter < Event > ( ) ;
329
+
323
330
_disabled : boolean = false ;
324
331
325
332
/**
@@ -374,6 +381,13 @@ export class MatSliderThumb implements AfterViewInit, ControlValueAccessor, OnIn
374
381
this . _blur . emit ( ) ;
375
382
}
376
383
384
+ _emitFakeEvent ( type : 'change' | 'input' ) {
385
+ const event = new Event ( type ) as any ;
386
+ event . isFake = true ;
387
+ const emitter = type === 'change' ? this . change : this . input ;
388
+ emitter . emit ( event ) ;
389
+ }
390
+
377
391
/**
378
392
* Sets the model value. Implemented as part of ControlValueAccessor.
379
393
* @param value
@@ -605,10 +619,11 @@ export class MatSlider extends _MatSliderMixinBase
605
619
readonly _cdr : ChangeDetectorRef ,
606
620
readonly _elementRef : ElementRef < HTMLElement > ,
607
621
private readonly _platform : Platform ,
622
+ readonly _globalChangeAndInputListener : GlobalChangeAndInputListener < 'input' | 'change' > ,
608
623
@Inject ( DOCUMENT ) document : any ,
609
624
@Optional ( ) private _dir : Directionality ,
610
625
@Optional ( ) @Inject ( MAT_RIPPLE_GLOBAL_OPTIONS )
611
- readonly _globalRippleOptions ?: RippleGlobalOptions ) {
626
+ readonly _globalRippleOptions ?: RippleGlobalOptions ) {
612
627
super ( _elementRef ) ;
613
628
this . _document = document ;
614
629
this . _window = this . _document . defaultView || window ;
@@ -756,6 +771,10 @@ export class MatSlider extends _MatSliderMixinBase
756
771
757
772
/** The MDCSliderAdapter implementation. */
758
773
class SliderAdapter implements MDCSliderAdapter {
774
+
775
+ /** The global change listener subscription used to handle change events on the slider inputs. */
776
+ changeSubscription : Subscription ;
777
+
759
778
constructor ( private readonly _delegate : MatSlider ) { }
760
779
761
780
// We manually assign functions instead of using prototype methods because
@@ -840,12 +859,22 @@ class SliderAdapter implements MDCSliderAdapter {
840
859
setPointerCapture = ( pointerId : number ) : void => {
841
860
this . _delegate . _elementRef . nativeElement . setPointerCapture ( pointerId ) ;
842
861
}
843
- // We ignore emitChangeEvent and emitInputEvent because the slider inputs
844
- // are already exposed so users can just listen for those events directly themselves.
845
862
emitChangeEvent = ( value : number , thumbPosition : Thumb ) : void => {
846
- this . _delegate . _getInput ( thumbPosition ) . _onChange ( value ) ;
863
+ // We block all real slider input change events and emit fake change events from here, instead.
864
+ // We do this because the mdc implementation of the slider does not trigger real change events
865
+ // on pointer up (only on left or right arrow key down).
866
+ //
867
+ // By stopping real change events from reaching users, and dispatching fake change events
868
+ // (which we allow to reach the user) the slider inputs change events are triggered at the
869
+ // appropriate times. This allows users to listen for change events directly on the slider
870
+ // input as they would with a native range input.
871
+ const input = this . _delegate . _getInput ( thumbPosition ) ;
872
+ input . _emitFakeEvent ( 'change' ) ;
873
+ input . _onChange ( value ) ;
874
+ }
875
+ emitInputEvent = ( value : number , thumbPosition : Thumb ) : void => {
876
+ this . _delegate . _getInput ( thumbPosition ) . _emitFakeEvent ( 'input' ) ;
847
877
}
848
- emitInputEvent = ( value : number , thumbPosition : Thumb ) : void => { } ;
849
878
emitDragStartEvent = ( value : number , thumbPosition : Thumb ) : void => {
850
879
const input = this . _delegate . _getInput ( thumbPosition ) ;
851
880
input . dragStart . emit ( { source : input , parent : this . _delegate , value } ) ;
@@ -872,11 +901,32 @@ class SliderAdapter implements MDCSliderAdapter {
872
901
}
873
902
registerInputEventHandler = < K extends EventType >
874
903
( thumbPosition : Thumb , evtType : K , handler : SpecificEventListener < K > ) : void => {
875
- this . _delegate . _getInputElement ( thumbPosition ) . addEventListener ( evtType , handler ) ;
904
+ if ( evtType === 'change' || evtType === 'input' ) {
905
+ this . changeSubscription = this . _delegate . _globalChangeAndInputListener
906
+ . listen ( evtType as 'change' | 'input' , ( event : Event ) => {
907
+ // We block all real change and input events and emit fake events from #emitChangeEvent
908
+ // and #emitInputEvent, instead. We do this because interacting with the MDC slider
909
+ // won't trigger all of the correct change and input events, but it will call
910
+ // #emitChangeEvent and #emitInputEvent at the correct times. This allows users to
911
+ // listen for these events directly on the slider input as they would with a native
912
+ // range input.
913
+ if ( event . target === this . _delegate . _getInputElement ( thumbPosition ) ) {
914
+ if ( ( event as any ) . isFake ) { return ; }
915
+ event . stopImmediatePropagation ( ) ;
916
+ handler ( event as GlobalEventHandlersEventMap [ K ] ) ;
917
+ }
918
+ } ) ;
919
+ } else {
920
+ this . _delegate . _getInputElement ( thumbPosition ) . addEventListener ( evtType , handler ) ;
921
+ }
876
922
}
877
923
deregisterInputEventHandler = < K extends EventType >
878
924
( thumbPosition : Thumb , evtType : K , handler : SpecificEventListener < K > ) : void => {
879
- this . _delegate . _getInputElement ( thumbPosition ) . removeEventListener ( evtType , handler ) ;
925
+ if ( evtType === 'change' ) {
926
+ this . changeSubscription . unsubscribe ( ) ;
927
+ } else {
928
+ this . _delegate . _getInputElement ( thumbPosition ) . removeEventListener ( evtType , handler ) ;
929
+ }
880
930
}
881
931
registerBodyEventHandler =
882
932
< K extends EventType > ( evtType : K , handler : SpecificEventListener < K > ) : void => {
0 commit comments