diff --git a/src/dev-app/mdc-slider/mdc-slider-demo.html b/src/dev-app/mdc-slider/mdc-slider-demo.html index e69de29bb2d1..7d170e878b0b 100644 --- a/src/dev-app/mdc-slider/mdc-slider-demo.html +++ b/src/dev-app/mdc-slider/mdc-slider-demo.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/material-experimental/mdc-slider/module.ts b/src/material-experimental/mdc-slider/module.ts index 4beea1a3b390..ba3e3367ad34 100644 --- a/src/material-experimental/mdc-slider/module.ts +++ b/src/material-experimental/mdc-slider/module.ts @@ -9,8 +9,7 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material-experimental/mdc-core'; -import {MatSlider} from './slider'; -import {MatSliderThumb} from './slider-thumb'; +import {MatSlider, MatSliderThumb} from './slider'; @NgModule({ imports: [MatCommonModule, CommonModule], diff --git a/src/material-experimental/mdc-slider/public-api.ts b/src/material-experimental/mdc-slider/public-api.ts index ee39eb6b8adf..294dbb8a1be7 100644 --- a/src/material-experimental/mdc-slider/public-api.ts +++ b/src/material-experimental/mdc-slider/public-api.ts @@ -6,6 +6,5 @@ * found in the LICENSE file at https://angular.io/license */ -export {MatSlider} from './slider'; -export {MatSliderThumb} from './slider-thumb'; +export {MatSlider, MatSliderThumb, MatSliderDragEvent} from './slider'; export {MatSliderModule} from './module'; diff --git a/src/material-experimental/mdc-slider/slider-adapter.ts b/src/material-experimental/mdc-slider/slider-adapter.ts deleted file mode 100644 index a3144d73f0d6..000000000000 --- a/src/material-experimental/mdc-slider/slider-adapter.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {SpecificEventListener, EventType} from '@material/base'; -import {MDCSliderAdapter, Thumb, TickMark} from '@material/slider'; - -export class SliderAdapter implements MDCSliderAdapter { - hasClass(className: string): boolean { - throw Error('Method not implemented.'); - } - addClass(className: string): void { - throw Error('Method not implemented.'); - } - removeClass(className: string): void { - throw Error('Method not implemented.'); - } - getAttribute(attribute: string): string | null { - throw Error('Method not implemented.'); - } - addThumbClass(className: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - removeThumbClass(className: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - getInputValue(thumb: Thumb): string { - throw Error('Method not implemented.'); - } - setInputValue(value: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - getInputAttribute(attribute: string, thumb: Thumb): string | null { - throw Error('Method not implemented.'); - } - setInputAttribute(attribute: string, value: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - removeInputAttribute(attribute: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - focusInput(thumb: Thumb): void { - throw Error('Method not implemented.'); - } - isInputFocused(thumb: Thumb): boolean { - throw Error('Method not implemented.'); - } - getThumbKnobWidth(thumb: Thumb): number { - throw Error('Method not implemented.'); - } - getThumbBoundingClientRect(thumb: Thumb): ClientRect { - throw Error('Method not implemented.'); - } - getBoundingClientRect(): ClientRect { - throw Error('Method not implemented.'); - } - isRTL(): boolean { - throw Error('Method not implemented.'); - } - setThumbStyleProperty(propertyName: string, value: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - removeThumbStyleProperty(propertyName: string, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - setTrackActiveStyleProperty(propertyName: string, value: string): void { - throw Error('Method not implemented.'); - } - removeTrackActiveStyleProperty(propertyName: string): void { - throw Error('Method not implemented.'); - } - setValueIndicatorText(value: number, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - getValueToAriaValueTextFn(): ((value: number) => string) | null { - throw Error('Method not implemented.'); - } - updateTickMarks(tickMarks: TickMark[]): void { - throw Error('Method not implemented.'); - } - setPointerCapture(pointerId: number): void { - throw Error('Method not implemented.'); - } - emitChangeEvent(value: number, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - emitInputEvent(value: number, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - emitDragStartEvent(value: number, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - emitDragEndEvent(value: number, thumb: Thumb): void { - throw Error('Method not implemented.'); - } - registerEventHandler(evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - deregisterEventHandler(evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - registerThumbEventHandler - (thumb: Thumb, evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - deregisterThumbEventHandler - (thumb: Thumb, evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - registerInputEventHandler - (thumb: Thumb, evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - deregisterInputEventHandler - (thumb: Thumb, evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - registerBodyEventHandler - (evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - deregisterBodyEventHandler - (evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - registerWindowEventHandler - (evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } - deregisterWindowEventHandler - (evtType: K, handler: SpecificEventListener): void { - throw Error('Method not implemented.'); - } -} diff --git a/src/material-experimental/mdc-slider/slider-thumb.ts b/src/material-experimental/mdc-slider/slider-thumb.ts deleted file mode 100644 index e9780a90ff2f..000000000000 --- a/src/material-experimental/mdc-slider/slider-thumb.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {NumberInput} from '@angular/cdk/coercion'; -import {DOCUMENT} from '@angular/common'; -import {Directive, ElementRef, EventEmitter, Inject, Input, Output} from '@angular/core'; -import {Thumb} from '@material/slider'; -import {MatSlider} from './slider'; - -/** - * Represents a drag event emitted by the MatSlider component. - */ -export interface MatSliderDragEvent { - /** The MatSliderThumb that was interacted with. */ - source: MatSliderThumb; - - /** The parent MatSlider that was interacted with. */ - parent: MatSlider; - - /** The current value of the slider. */ - value: number; - - /** The thumb that was interacted with. */ - thumb: Thumb; -} - -/** - * The native input used by the MatSlider. - */ -@Directive({ - selector: 'input[mat-slider-thumb]', - host: { - 'class': 'mdc-slider__input', - 'type': 'range', - '[min]': 'min', - '[max]': 'max', - '[step]': 'step', - '[attr.value]': 'value', - '(blur)': '_blur.emit()', - '(focus)': '_focus.emit()', - } -}) export class MatSliderThumb { - /** The current value of this slider input. */ - @Input() - get value(): number { return this._value; } - set value(v: number) { this._value = v; } - private _value: number; - - /** The minimum value that this slider input can have. */ - @Input() - get min(): number { return 0; } - set min(v: number) { throw Error('Invalid attribute "min" on MatSliderThumb.'); } - - /** The maximum value that this slider input can have. */ - @Input() - get max(): number { return 100; } - set max(v: number) { throw Error('Invalid attribute "max" on MatSliderThumb.'); } - - /** The size of each increment between the values of the slider. */ - @Input() - get step(): number { return 1; } - set step(v: number) { throw Error('Invalid attribute "step" on MatSliderThumb.'); } - - /** MDC Slider does not use the disabled attribute it's native inputs. */ - @Input() - set disabled(v: boolean) { throw Error('Invalid attribute "disabled" on MatSliderThumb.'); } - - /** Event emitted when the slider thumb starts being dragged. */ - @Output() readonly dragStart: EventEmitter - = new EventEmitter(); - - /** Event emitted when the slider thumb stops being dragged. */ - @Output() readonly dragEnd: EventEmitter - = new EventEmitter(); - - /** Event emitted every time the MatSliderThumb is blurred. */ - @Output() readonly _blur: EventEmitter = new EventEmitter(); - - /** Event emitted every time the MatSliderThumb is focused. */ - @Output() readonly _focus: EventEmitter = new EventEmitter(); - - /** Indicates which slider thumb this input corresponds to. */ - thumb: Thumb; - - constructor( - @Inject(DOCUMENT) private readonly _document: Document, - readonly _elementRef: ElementRef, - ) {} - - /** Returns true if this slider input currently has focus. */ - _isFocused(): boolean { - return this._document.activeElement === this._elementRef.nativeElement; - } - - static ngAcceptInputType_value: NumberInput; -} diff --git a/src/material-experimental/mdc-slider/slider.html b/src/material-experimental/mdc-slider/slider.html index 991a22bc27c4..b8f42c85be02 100644 --- a/src/material-experimental/mdc-slider/slider.html +++ b/src/material-experimental/mdc-slider/slider.html @@ -16,9 +16,7 @@
- - {{_getValueIndicatorTextByThumb(thumb)}} - + {{_getValueIndicatorText(thumb)}}
diff --git a/src/material-experimental/mdc-slider/slider.scss b/src/material-experimental/mdc-slider/slider.scss index e69de29bb2d1..edbdb0402de2 100644 --- a/src/material-experimental/mdc-slider/slider.scss +++ b/src/material-experimental/mdc-slider/slider.scss @@ -0,0 +1,6 @@ +@import '@material/slider/slider'; +@include core-styles; + +.mdc-slider { + display: block; +} diff --git a/src/material-experimental/mdc-slider/slider.ts b/src/material-experimental/mdc-slider/slider.ts index 2c06e7b736a8..5283903f42c2 100644 --- a/src/material-experimental/mdc-slider/slider.ts +++ b/src/material-experimental/mdc-slider/slider.ts @@ -20,18 +20,181 @@ import { ChangeDetectorRef, Component, ContentChildren, + Directive, ElementRef, + EventEmitter, Inject, Input, OnDestroy, + Output, QueryList, ViewChild, ViewChildren, ViewEncapsulation, } from '@angular/core'; -import {MDCSliderFoundation, Thumb, TickMark} from '@material/slider'; -import {SliderAdapter} from './slider-adapter'; -import {MatSliderThumb} from './slider-thumb'; +import {SpecificEventListener, EventType} from '@material/base'; +import {MDCSliderAdapter, MDCSliderFoundation, Thumb, TickMark} from '@material/slider'; + +/** Represents a drag event emitted by the MatSlider component. */ +export interface MatSliderDragEvent { + /** The MatSliderThumb that was interacted with. */ + source: MatSliderThumb; + + /** The MatSlider that was interacted with. */ + parent: MatSlider; + + /** The current value of the slider. */ + value: number; +} + +/** + * Directive that adds slider-specific behaviors to an input element inside ``. + * Up to two may be placed inside of a ``. + * + * If one is used, the selector `matSliderThumb` must be used, and the outcome will be a normal + * slider. If two are used, the selectors `matSliderStartThumb` and `matSliderEndThumb` must be + * used, and the outcome will be a range slider with two slider thumbs. + */ +@Directive({ + selector: 'input[matSliderThumb], input[matSliderStartThumb], input[matSliderEndThumb]', + host: { + 'class': 'mdc-slider__input', + 'type': 'range', + '(blur)': '_blur.emit()', + '(focus)': '_focus.emit()', + }, +}) +export class MatSliderThumb implements AfterViewInit { + + // ** IMPORTANT NOTE ** + // + // The way `value` is implemented for MatSliderThumb doesn't follow typical Angular conventions. + // Normally we would define a private variable `_value` as the source of truth for the value of + // the slider thumb input. The source of truth for the value of the slider inputs has already + // been decided for us by MDC to be the value attribute on the slider thumb inputs. This is + // because the MDC foundation and adapter expect that the value attribute is the source of truth + // for the slider inputs. + // + // Also, note that the value attribute is completely disconnected from the value property. + + /** The current value of this slider input. */ + @Input() + get value(): number { + return coerceNumberProperty(this._elementRef.nativeElement.getAttribute('value')); + } + set value(v: number) { + const value = coerceNumberProperty(v); + + // If the foundation has already been initialized, we need to + // relay any value updates to it so that it can update the UI. + if (this._slider._initialized) { + this._slider._setValue(value, this._thumbPosition); + } else { + // Setup for the MDC foundation. + this._elementRef.nativeElement.setAttribute('value', `${value}`); + } + } + + /** Event emitted when the slider thumb starts being dragged. */ + @Output() readonly dragStart: EventEmitter + = new EventEmitter(); + + /** Event emitted when the slider thumb stops being dragged. */ + @Output() readonly dragEnd: EventEmitter + = new EventEmitter(); + + /** Event emitted every time the MatSliderThumb is blurred. */ + @Output() readonly _blur: EventEmitter = new EventEmitter(); + + /** Event emitted every time the MatSliderThumb is focused. */ + @Output() readonly _focus: EventEmitter = new EventEmitter(); + + /** Indicates which slider thumb this input corresponds to. */ + private _thumbPosition: Thumb = this._elementRef.nativeElement.hasAttribute('matSliderStartThumb') + ? Thumb.START + : Thumb.END; + + private _document: Document; + + constructor( + @Inject(DOCUMENT) document: any, + private readonly _slider: MatSlider, + readonly _elementRef: ElementRef, + ) { + this._document = document; + // By calling this in the constructor we guarantee that the sibling sliders initial value by + // has already been set by the time we reach ngAfterViewInit(). + this._initializeInputValueAttribute(); + } + + ngAfterViewInit() { + this._initializeInputMinMax(); + this._initializeInputValueProperty(); + + // Setup for the MDC foundation. + if (this._slider.disabled) { + this._elementRef.nativeElement.disabled = true; + } + } + + /** Returns true if this slider input currently has focus. */ + _isFocused(): boolean { + return this._document.activeElement === this._elementRef.nativeElement; + } + + /** + * Sets the min and max properties on the slider thumb input. + * + * Must be called AFTER the sibling slider thumb input is guaranteed to have had its value + * attribute value set. For a range slider, the min and max of the slider thumb input depends on + * the value of its sibling slider thumb inputs value. + * + * Must be called BEFORE the value property is set. In the case where the min and max have not + * yet been set and we are setting the input value property to a value outside of the native + * inputs default min or max. The value property would not be set to our desired value, but + * instead be capped at either the default min or max. + * + */ + private _initializeInputMinMax(): void { + const min = this._elementRef.nativeElement.hasAttribute('matSliderEndThumb') + ? this._slider._getInput(Thumb.START).value + : this._slider.min; + const max = this._elementRef.nativeElement.hasAttribute('matSliderStartThumb') + ? this._slider._getInput(Thumb.END).value + : this._slider.max; + this._elementRef.nativeElement.min = `${min}`; + this._elementRef.nativeElement.max = `${max}`; + } + + /** + * Sets the value property on the slider thumb input. + * + * Must be called AFTER the min and max have been set. In the case where the min and max have not + * yet been set and we are setting the input value property to a value outside of the native + * inputs default min or max. The value property would not be set to our desired value, but + * instead be capped at either the default min or max. + */ + private _initializeInputValueProperty(): void { + this._elementRef.nativeElement.value = `${this.value}`; + } + + /** + * Ensures the value attribute is initialized. + * + * Must be called BEFORE the min and max are set. For a range slider, the min and max of the + * slider thumb input depends on the value of its sibling slider thumb inputs value. + */ + private _initializeInputValueAttribute(): void { + // Only set the default value if an initial value has not already been provided. + if (!this._elementRef.nativeElement.hasAttribute('value')) { + this.value = this._elementRef.nativeElement.hasAttribute('matSliderEndThumb') + ? this._slider.max + : this._slider.min; + } + } + + static ngAcceptInputType_value: NumberInput; +} /** * Allows users to select from a range of values by moving the slider thumb. It is similar in @@ -59,11 +222,16 @@ export class MatSlider implements AfterViewInit, OnDestroy { /** The slider thumb knob(s) */ @ViewChildren('knob') _knobs: QueryList>; + /** The span containing the slider thumb value indicator text */ + @ViewChildren('valueIndicatorTextElement') + _valueIndicatorTextElements: QueryList>; + /** The active section of the slider track. */ @ViewChild('trackActive') _trackActive: ElementRef; /** The sliders hidden range input(s). */ - @ContentChildren(MatSliderThumb, {descendants: false}) _inputs: QueryList; + @ContentChildren(MatSliderThumb, {descendants: false}) + _inputs: QueryList; /** Whether the slider is disabled. */ @Input() @@ -89,22 +257,20 @@ export class MatSlider implements AfterViewInit, OnDestroy { /** Whether the slider displays tick marks along the slider track. */ @Input() get showTickMarks(): boolean { return this._showTickMarks; } - set showTickMarks(v: boolean) { - this._showTickMarks = coerceBooleanProperty(v); - } + set showTickMarks(v: boolean) { this._showTickMarks = coerceBooleanProperty(v); } private _showTickMarks: boolean = false; /** The minimum value that the slider can have. */ @Input() get min(): number { return this._min; } set min(v: number) { this._min = coerceNumberProperty(v, this._min); } - private _min = 0; + private _min: number = 0; /** The maximum value that the slider can have. */ @Input() get max(): number { return this._max; } set max(v: number) { this._max = coerceNumberProperty(v, this._max); } - private _max = 100; + private _max: number = 100; /** The values at which the thumb will snap. */ @Input() @@ -120,17 +286,11 @@ export class MatSlider implements AfterViewInit, OnDestroy { @Input() displayWith: ((value: number) => string) | null; /** Instance of the MDC slider foundation for this slider. */ - private _foundation = new MDCSliderFoundation(new SliderAdapter()); + private _foundation = new MDCSliderFoundation(new SliderAdapter(this)); /** Whether the foundation has been initialized. */ _initialized: boolean = false; - /** The string representation of the start thumbs value. */ - _startValueIndicatorText: string; - - /** The string representation of the end thumbs value. */ - _endValueIndicatorText: string; - /** The injected document if available or fallback to the global document reference. */ _document: Document; @@ -140,28 +300,48 @@ export class MatSlider implements AfterViewInit, OnDestroy { */ _window: Window; - /** The hosts native HTML element. */ - _hostElement: HTMLElement; - /** Used to keep track of & render the active & inactive tick marks on the slider track. */ _tickMarks: TickMark[]; + /** The display value of the start thumb. */ + private _startValueIndicatorText: string; + + /** The display value of the end thumb. */ + private _endValueIndicatorText: string; + constructor( readonly _cdr: ChangeDetectorRef, - private readonly _elementRef: ElementRef, + readonly _elementRef: ElementRef, private readonly _platform: Platform, @Inject(DOCUMENT) document: any) { this._document = document; this._window = this._document.defaultView || window; - this._hostElement = this._elementRef.nativeElement; } ngAfterViewInit() { - this._foundation.init(); + if (typeof ngDevMode === 'undefined' || ngDevMode) { + _validateInputs( + this._isRange(), + this._getInputElement(Thumb.START), + this._getInputElement(Thumb.END), + ); + } if (this._platform.isBrowser) { + this._foundation.init(); this._foundation.layout(); + this._initialized = true; } - this._initialized = true; + // The MDC foundation requires access to the view and content children of the MatSlider. In + // order to access the view and content children of MatSlider we need to wait until change + // detection runs and materializes them. That is why we call init() and layout() in + // ngAfterViewInit(). + // + // The MDC foundation then uses the information it gathers from the DOM to compute an initial + // value for the tickMarks array. It then tries to update the component data, but because it is + // updating the component data AFTER change detection already ran, we will get a changed after + // checked error. Because of this, we need to force change detection to update the UI with the + // new state. + this._cdr.detectChanges(); } ngOnDestroy() { @@ -170,16 +350,9 @@ export class MatSlider implements AfterViewInit, OnDestroy { } } - /** Gets the current value of given slider thumb. */ - _getValue(thumb: Thumb): number { - return thumb === Thumb.START - ? this._foundation.getValueStart() - : this._foundation.getValue(); - } - /** Sets the value of a slider thumb. */ - _setValue(value: number, thumb: Thumb): void { - thumb === Thumb.START + _setValue(value: number, thumbPosition: Thumb): void { + thumbPosition === Thumb.START ? this._foundation.setValueStart(value) : this._foundation.setValue(value); } @@ -189,41 +362,45 @@ export class MatSlider implements AfterViewInit, OnDestroy { return this._inputs.length === 2; } - /** Gets the slider thumb input of the given thumb. */ - _getInput(thumb: Thumb): MatSliderThumb { - return thumb === Thumb.END ? this._inputs.get(this._inputs.length - 1)! : this._inputs.get(0)!; + /** Gets the slider thumb input of the given thumb position. */ + _getInput(thumbPosition: Thumb): MatSliderThumb { + return thumbPosition === Thumb.END ? this._inputs.last! : this._inputs.first!; + } + + /** Gets the slider thumb HTML input element of the given thumb position. */ + _getInputElement(thumbPosition: Thumb): HTMLInputElement { + return this._getInput(thumbPosition)._elementRef.nativeElement; } - /** Gets the slider thumb HTML input element of the given thumb. */ - _getInputElement(thumb: Thumb): HTMLInputElement { - return this._getInput(thumb)._elementRef.nativeElement; + /** Gets the slider thumb HTML element of the given thumb position. */ + _getThumbElement(thumbPosition: Thumb): HTMLElement { + const thumbElementRef = thumbPosition === Thumb.END ? this._thumbs.last : this._thumbs.first; + return thumbElementRef.nativeElement; } - /** Gets the slider thumb HTML element of the given thumb. */ - _getThumbElement(thumb: Thumb): HTMLElement { - const thumbs = this._thumbs.toArray().map(e => e.nativeElement); - return thumb === Thumb.END ? thumbs[thumbs.length - 1] : thumbs[0]; + /** Gets the slider knob HTML element of the given thumb position. */ + _getKnobElement(thumbPosition: Thumb): HTMLElement { + const knobElementRef = thumbPosition === Thumb.END ? this._knobs.last : this._knobs.first; + return knobElementRef.nativeElement; } - /** Gets the slider knob HTML element of the given thumb. */ - _getKnobElement(thumb: Thumb): HTMLElement { - const knobs = this._knobs.toArray().map(e => e.nativeElement); - return thumb === Thumb.END ? knobs[knobs.length - 1] : knobs[0]; + _getValueIndicatorText(thumbPosition: Thumb) { + return thumbPosition === Thumb.START + ? this._startValueIndicatorText + : this._endValueIndicatorText; } /** - * Gets the text representation of the given value. + * Sets the value indicator text of the given thumb position using the given value. * - * Uses the `displayWith` function if one has been provided. Otherwise, it just returns the - * current numeric value as a string. + * Uses the `displayWith` function if one has been provided. Otherwise, it just uses the + * numeric value as a string. */ - _getValueIndicatorText(value: number): string { - return this.displayWith ? this.displayWith(value) : value.toString(); - } - - /** Gets the text representation of the current value of the given thumb. */ - _getValueIndicatorTextByThumb(thumb: Thumb): string { - return this._getValueIndicatorText(this._getValue(thumb)); + _setValueIndicatorText(value: number, thumbPosition: Thumb) { + const valueText = this.displayWith ? this.displayWith(value) : `${value}`; + thumbPosition === Thumb.START + ? this._startValueIndicatorText = valueText + : this._endValueIndicatorText = valueText; } /** Determines the class name for a HTML element. */ @@ -245,3 +422,183 @@ export class MatSlider implements AfterViewInit, OnDestroy { static ngAcceptInputType_max: NumberInput; static ngAcceptInputType_step: NumberInput; } + +/** The MDCSliderAdapter implementation. */ +class SliderAdapter implements MDCSliderAdapter { + constructor(private readonly _delegate: MatSlider) {} + + // We manually assign functions instead of using prototype methods because + // MDC clobbers the values otherwise. + // See https://github.com/material-components/material-components-web/pull/6256 + + hasClass = (className: string): boolean => { + return this._delegate._elementRef.nativeElement.classList.contains(className); + } + addClass = (className: string): void => { + this._delegate._elementRef.nativeElement.classList.add(className); + } + removeClass = (className: string): void => { + this._delegate._elementRef.nativeElement.classList.remove(className); + } + getAttribute = (attribute: string): string | null => { + return this._delegate._elementRef.nativeElement.getAttribute(attribute); + } + addThumbClass = (className: string, thumbPosition: Thumb): void => { + this._delegate._getThumbElement(thumbPosition).classList.add(className); + } + removeThumbClass = (className: string, thumbPosition: Thumb): void => { + this._delegate._getThumbElement(thumbPosition).classList.remove(className); + } + getInputValue = (thumbPosition: Thumb): string => { + return this._delegate._getInputElement(thumbPosition).value; + } + setInputValue = (value: string, thumbPosition: Thumb): void => { + this._delegate._getInputElement(thumbPosition).value = value; + } + getInputAttribute = (attribute: string, thumbPosition: Thumb): string | null => { + return this._delegate._getInputElement(thumbPosition).getAttribute(attribute); + } + setInputAttribute = (attribute: string, value: string, thumbPosition: Thumb): void => { + this._delegate._getInputElement(thumbPosition).setAttribute(attribute, value); + } + removeInputAttribute = (attribute: string, thumbPosition: Thumb): void => { + this._delegate._getInputElement(thumbPosition).removeAttribute(attribute); + } + focusInput = (thumbPosition: Thumb): void => { + this._delegate._getInputElement(thumbPosition).focus(); + } + isInputFocused = (thumbPosition: Thumb): boolean => { + return this._delegate._getInput(thumbPosition)._isFocused(); + } + getThumbKnobWidth = (thumbPosition: Thumb): number => { + // TODO(wagnermaciel): Check if this causes issues for SSR + // once the mdc-slider is added back to the kitchen sink SSR app. + return this._delegate._getKnobElement(thumbPosition).getBoundingClientRect().width; + } + getThumbBoundingClientRect = (thumbPosition: Thumb): ClientRect => { + return this._delegate._getThumbElement(thumbPosition).getBoundingClientRect(); + } + getBoundingClientRect = (): ClientRect => { + return this._delegate._elementRef.nativeElement.getBoundingClientRect(); + } + isRTL = (): boolean => { + // TODO(wagnermaciel): Actually implementing this. + return false; + } + setThumbStyleProperty = (propertyName: string, value: string, thumbPosition: Thumb): void => { + this._delegate._getThumbElement(thumbPosition).style.setProperty(propertyName, value); + } + removeThumbStyleProperty = (propertyName: string, thumbPosition: Thumb): void => { + this._delegate._getThumbElement(thumbPosition).style.removeProperty(propertyName); + } + setTrackActiveStyleProperty = (propertyName: string, value: string): void => { + this._delegate._trackActive.nativeElement.style.setProperty(propertyName, value); + } + removeTrackActiveStyleProperty = (propertyName: string): void => { + this._delegate._trackActive.nativeElement.style.removeProperty(propertyName); + } + setValueIndicatorText = (value: number, thumbPosition: Thumb): void => { + this._delegate._setValueIndicatorText(value, thumbPosition); + } + getValueToAriaValueTextFn = (): ((value: number) => string) | null => { + return this._delegate.displayWith; + } + updateTickMarks = (tickMarks: TickMark[]): void => { + this._delegate._tickMarks = tickMarks; + this._delegate._cdr.markForCheck(); + } + setPointerCapture = (pointerId: number): void => { + this._delegate._elementRef.nativeElement.setPointerCapture(pointerId); + } + // We ignore emitChangeEvent and emitInputEvent because the slider inputs + // are already exposed so users can just listen for those events directly themselves. + emitChangeEvent = (value: number, thumbPosition: Thumb): void => {}; + emitInputEvent = (value: number, thumbPosition: Thumb): void => {}; + emitDragStartEvent = (value: number, thumbPosition: Thumb): void => { + const input = this._delegate._getInput(thumbPosition); + input.dragStart.emit({ source: input, parent: this._delegate, value }); + } + emitDragEndEvent = (value: number, thumbPosition: Thumb): void => { + const input = this._delegate._getInput(thumbPosition); + input.dragEnd.emit({ source: input, parent: this._delegate, value }); + } + registerEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._elementRef.nativeElement.addEventListener(evtType, handler); + } + deregisterEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._elementRef.nativeElement.removeEventListener(evtType, handler); + } + registerThumbEventHandler = + (thumbPosition: Thumb, evtType: K, handler: SpecificEventListener): void => { + this._delegate._getThumbElement(thumbPosition).addEventListener(evtType, handler); + } + deregisterThumbEventHandler = + (thumbPosition: Thumb, evtType: K, handler: SpecificEventListener): void => { + this._delegate._getThumbElement(thumbPosition).removeEventListener(evtType, handler); + } + registerInputEventHandler = + (thumbPosition: Thumb, evtType: K, handler: SpecificEventListener): void => { + this._delegate._getInputElement(thumbPosition).addEventListener(evtType, handler); + } + deregisterInputEventHandler = + (thumbPosition: Thumb, evtType: K, handler: SpecificEventListener): void => { + this._delegate._getInputElement(thumbPosition).removeEventListener(evtType, handler); + } + registerBodyEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._document.body.addEventListener(evtType, handler); + } + deregisterBodyEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._document.body.removeEventListener(evtType, handler); + } + registerWindowEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._window.addEventListener(evtType, handler); + } + deregisterWindowEventHandler = + (evtType: K, handler: SpecificEventListener): void => { + this._delegate._window.removeEventListener(evtType, handler); + } +} + +/** + * Ensures that there is not an invalid configuration for the slider thumb inputs. + */ +function _validateInputs( + isRange: boolean, + startInputElement: HTMLInputElement, + endInputElement: HTMLInputElement): void { + if (isRange) { + if (!startInputElement.hasAttribute('matSliderStartThumb')) { + _throwInvalidInputConfigurationError(); + } + if (!endInputElement.hasAttribute('matSliderEndThumb')) { + _throwInvalidInputConfigurationError(); + } + } else { + if (!endInputElement.hasAttribute('matSliderThumb')) { + _throwInvalidInputConfigurationError(); + } + } +} + +function _throwInvalidInputConfigurationError(): void { + throw Error(`Invalid slider thumb input configuration! + + Valid configurations are as follows: + + + + + + or + + + + + + `); +}