Skip to content

Commit d3adff0

Browse files
committed
feat(material-experimental/mdc-slider): implement MatSlider (#21680)
1 parent 0706f59 commit d3adff0

File tree

5 files changed

+271
-16
lines changed

5 files changed

+271
-16
lines changed

src/material-experimental/mdc-slider/module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {MatSliderThumb} from './slider-thumb';
1414

1515
@NgModule({
1616
imports: [MatCommonModule, CommonModule],
17-
exports: [MatSlider],
17+
exports: [MatSlider, MatSliderThumb],
1818
declarations: [
1919
MatSlider,
2020
MatSliderThumb,

src/material-experimental/mdc-slider/slider-thumb.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ import {NumberInput} from '@angular/cdk/coercion';
1010
import {DOCUMENT} from '@angular/common';
1111
import {Directive, ElementRef, EventEmitter, Inject, Input, Output} from '@angular/core';
1212
import {Thumb} from '@material/slider';
13+
import {MatSlider} from './slider';
14+
15+
/**
16+
* Represents a drag event emitted by the MatSlider component.
17+
*/
18+
export interface MatSliderDragEvent {
19+
/** The MatSliderThumb that was interacted with. */
20+
source: MatSliderThumb;
21+
22+
/** The parent MatSlider that was interacted with. */
23+
parent: MatSlider;
24+
25+
/** The current value of the slider. */
26+
value: number;
27+
28+
/** The thumb that was interacted with. */
29+
thumb: Thumb;
30+
}
1331

1432
/**
1533
* The native input used by the MatSlider.
@@ -52,6 +70,14 @@ import {Thumb} from '@material/slider';
5270
@Input()
5371
set disabled(v: boolean) { throw Error('Invalid attribute "disabled" on MatSliderThumb.'); }
5472

73+
/** Event emitted when the slider thumb starts being dragged. */
74+
@Output() readonly dragStart: EventEmitter<MatSliderDragEvent>
75+
= new EventEmitter<MatSliderDragEvent>();
76+
77+
/** Event emitted when the slider thumb stops being dragged. */
78+
@Output() readonly dragEnd: EventEmitter<MatSliderDragEvent>
79+
= new EventEmitter<MatSliderDragEvent>();
80+
5581
/** Event emitted every time the MatSliderThumb is blurred. */
5682
@Output() readonly _blur: EventEmitter<void> = new EventEmitter<void>();
5783

@@ -63,17 +89,12 @@ import {Thumb} from '@material/slider';
6389

6490
constructor(
6591
@Inject(DOCUMENT) private readonly _document: Document,
66-
private readonly _elementRef: ElementRef<HTMLInputElement>,
92+
readonly _elementRef: ElementRef<HTMLInputElement>,
6793
) {}
6894

69-
/** Returns the hosts native HTML element. */
70-
_getHostElement(): HTMLInputElement {
71-
return this._elementRef.nativeElement;
72-
}
73-
7495
/** Returns true if this slider input currently has focus. */
7596
_isFocused(): boolean {
76-
return this._document.activeElement === this._getHostElement();
97+
return this._document.activeElement === this._elementRef.nativeElement;
7798
}
7899

79100
static ngAcceptInputType_value: NumberInput;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!-- Inputs -->
2+
<ng-content></ng-content>
3+
4+
<!-- Track -->
5+
<div class="mdc-slider__track">
6+
<div class="mdc-slider__track--inactive"></div>
7+
<div class="mdc-slider__track--active">
8+
<div class="mdc-slider__track--active_fill" #trackActive></div>
9+
</div>
10+
<div *ngIf="showTickMarks" class="mdc-slider__tick-marks" #tickMarkContainer>
11+
<div *ngFor="let tickMark of _tickMarks" [class]="_getTickMarkClass(tickMark)"></div>
12+
</div>
13+
</div>
14+
15+
<!-- Thumbs -->
16+
<div class="mdc-slider__thumb" *ngFor="let thumb of _getThumbTypes()" #thumb>
17+
<div class="mdc-slider__value-indicator-container" *ngIf="discrete">
18+
<div class="mdc-slider__value-indicator">
19+
<span class="mdc-slider__value-indicator-text">
20+
{{_getValueIndicatorTextByThumb(thumb)}}
21+
</span>
22+
</div>
23+
</div>
24+
<div class="mdc-slider__thumb-knob" #knob></div>
25+
</div>

src/material-experimental/mdc-slider/slider.ts

Lines changed: 217 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,31 @@
77
*/
88

99
import {
10+
BooleanInput,
11+
coerceBooleanProperty,
12+
coerceNumberProperty,
13+
NumberInput
14+
} from '@angular/cdk/coercion';
15+
import {Platform} from '@angular/cdk/platform';
16+
import {DOCUMENT} from '@angular/common';
17+
import {
18+
AfterViewInit,
1019
ChangeDetectionStrategy,
20+
ChangeDetectorRef,
1121
Component,
22+
ContentChildren,
23+
ElementRef,
24+
Inject,
25+
Input,
26+
OnDestroy,
27+
QueryList,
28+
ViewChild,
29+
ViewChildren,
1230
ViewEncapsulation,
1331
} from '@angular/core';
14-
import {MDCSliderFoundation} from '@material/slider';
32+
import {MDCSliderFoundation, Thumb, TickMark} from '@material/slider';
1533
import {SliderAdapter} from './slider-adapter';
34+
import {MatSliderThumb} from './slider-thumb';
1635

1736
/**
1837
* Allows users to select from a range of values by moving the slider thumb. It is similar in
@@ -22,13 +41,207 @@ import {SliderAdapter} from './slider-adapter';
2241
selector: 'mat-slider',
2342
templateUrl: 'slider.html',
2443
styleUrls: ['slider.css'],
44+
host: {
45+
'class': 'mat-mdc-slider mdc-slider',
46+
'[class.mdc-slider--range]': '_isRange()',
47+
'[class.mdc-slider--disabled]': 'disabled',
48+
'[class.mdc-slider--discrete]': 'discrete',
49+
'[class.mdc-slider--tick-marks]': 'showTickMarks',
50+
},
51+
exportAs: 'matSlider',
2552
changeDetection: ChangeDetectionStrategy.OnPush,
2653
encapsulation: ViewEncapsulation.None,
2754
})
28-
export class MatSlider {
55+
export class MatSlider implements AfterViewInit, OnDestroy {
56+
/** The slider thumb(s). */
57+
@ViewChildren('thumb') _thumbs: QueryList<ElementRef<HTMLElement>>;
2958

30-
/** Instance of the MDC slider foundation for this slider. */
59+
/** The slider thumb knob(s) */
60+
@ViewChildren('knob') _knobs: QueryList<ElementRef<HTMLElement>>;
61+
62+
/** The active section of the slider track. */
63+
@ViewChild('trackActive') _trackActive: ElementRef<HTMLElement>;
64+
65+
/** The sliders hidden range input(s). */
66+
@ContentChildren(MatSliderThumb, {descendants: false}) _inputs: QueryList<MatSliderThumb>;
67+
68+
/** Whether the slider is disabled. */
69+
@Input()
70+
get disabled(): boolean { return this._disabled; }
71+
set disabled(v: boolean) {
72+
this._disabled = coerceBooleanProperty(v);
73+
74+
// If we want to disable the slider after the foundation has been initialized,
75+
// we need to inform the foundation by calling `setDisabled`. Also, we can't call
76+
// this before initializing the foundation because it will throw errors.
77+
if (this._initialized) {
78+
this._foundation.setDisabled(v);
79+
}
80+
}
81+
private _disabled: boolean = false;
82+
83+
/** Whether the slider displays a numeric value label upon pressing the thumb. */
84+
@Input()
85+
get discrete(): boolean { return this._discrete; }
86+
set discrete(v: boolean) { this._discrete = coerceBooleanProperty(v); }
87+
private _discrete: boolean = false;
88+
89+
/** Whether the slider displays tick marks along the slider track. */
90+
@Input()
91+
get showTickMarks(): boolean { return this._showTickMarks; }
92+
set showTickMarks(v: boolean) {
93+
this._showTickMarks = coerceBooleanProperty(v);
94+
}
95+
private _showTickMarks: boolean = false;
96+
97+
/** The minimum value that the slider can have. */
98+
@Input()
99+
get min(): number { return this._min; }
100+
set min(v: number) { this._min = coerceNumberProperty(v, this._min); }
101+
private _min = 0;
31102

32-
// tslint:disable-next-line:no-unused-variable
103+
/** The maximum value that the slider can have. */
104+
@Input()
105+
get max(): number { return this._max; }
106+
set max(v: number) { this._max = coerceNumberProperty(v, this._max); }
107+
private _max = 100;
108+
109+
/** The values at which the thumb will snap. */
110+
@Input()
111+
get step(): number { return this._step; }
112+
set step(v: number) { this._step = coerceNumberProperty(v, this._step); }
113+
private _step: number = 1;
114+
115+
/**
116+
* Function that will be used to format the value before it is displayed
117+
* in the thumb label. Can be used to format very large number in order
118+
* for them to fit into the slider thumb.
119+
*/
120+
@Input() displayWith: ((value: number) => string) | null;
121+
122+
/** Instance of the MDC slider foundation for this slider. */
33123
private _foundation = new MDCSliderFoundation(new SliderAdapter());
124+
125+
/** Whether the foundation has been initialized. */
126+
_initialized: boolean = false;
127+
128+
/** The string representation of the start thumbs value. */
129+
_startValueIndicatorText: string;
130+
131+
/** The string representation of the end thumbs value. */
132+
_endValueIndicatorText: string;
133+
134+
/** The injected document if available or fallback to the global document reference. */
135+
_document: Document;
136+
137+
/**
138+
* The defaultView of the injected document if
139+
* available or fallback to global window reference.
140+
*/
141+
_window: Window;
142+
143+
/** The hosts native HTML element. */
144+
_hostElement: HTMLElement;
145+
146+
/** Used to keep track of & render the active & inactive tick marks on the slider track. */
147+
_tickMarks: TickMark[];
148+
149+
constructor(
150+
readonly _cdr: ChangeDetectorRef,
151+
private readonly _elementRef: ElementRef<HTMLElement>,
152+
private readonly _platform: Platform,
153+
@Inject(DOCUMENT) document: any) {
154+
this._document = document;
155+
this._window = this._document.defaultView || window;
156+
this._hostElement = this._elementRef.nativeElement;
157+
}
158+
159+
ngAfterViewInit() {
160+
this._foundation.init();
161+
if (this._platform.isBrowser) {
162+
this._foundation.layout();
163+
}
164+
this._initialized = true;
165+
}
166+
167+
ngOnDestroy() {
168+
if (this._platform.isBrowser) {
169+
this._foundation.destroy();
170+
}
171+
}
172+
173+
/** Gets the current value of given slider thumb. */
174+
_getValue(thumb: Thumb): number {
175+
return thumb === Thumb.START
176+
? this._foundation.getValueStart()
177+
: this._foundation.getValue();
178+
}
179+
180+
/** Sets the value of a slider thumb. */
181+
_setValue(value: number, thumb: Thumb): void {
182+
thumb === Thumb.START
183+
? this._foundation.setValueStart(value)
184+
: this._foundation.setValue(value);
185+
}
186+
187+
/** Whether this is a ranged slider. */
188+
_isRange(): boolean {
189+
return this._inputs.length === 2;
190+
}
191+
192+
/** Gets the slider thumb input of the given thumb. */
193+
_getInput(thumb: Thumb): MatSliderThumb {
194+
return thumb === Thumb.END ? this._inputs.get(this._inputs.length - 1)! : this._inputs.get(0)!;
195+
}
196+
197+
/** Gets the slider thumb HTML input element of the given thumb. */
198+
_getInputElement(thumb: Thumb): HTMLInputElement {
199+
return this._getInput(thumb)._elementRef.nativeElement;
200+
}
201+
202+
/** Gets the slider thumb HTML element of the given thumb. */
203+
_getThumbElement(thumb: Thumb): HTMLElement {
204+
const thumbs = this._thumbs.toArray().map(e => e.nativeElement);
205+
return thumb === Thumb.END ? thumbs[thumbs.length - 1] : thumbs[0];
206+
}
207+
208+
/** Gets the slider knob HTML element of the given thumb. */
209+
_getKnobElement(thumb: Thumb): HTMLElement {
210+
const knobs = this._knobs.toArray().map(e => e.nativeElement);
211+
return thumb === Thumb.END ? knobs[knobs.length - 1] : knobs[0];
212+
}
213+
214+
/**
215+
* Gets the text representation of the given value.
216+
*
217+
* Uses the `displayWith` function if one has been provided. Otherwise, it just returns the
218+
* current numeric value as a string.
219+
*/
220+
_getValueIndicatorText(value: number): string {
221+
return this.displayWith ? this.displayWith(value) : value.toString();
222+
}
223+
224+
/** Gets the text representation of the current value of the given thumb. */
225+
_getValueIndicatorTextByThumb(thumb: Thumb): string {
226+
return this._getValueIndicatorText(this._getValue(thumb));
227+
}
228+
229+
/** Determines the class name for a HTML element. */
230+
_getTickMarkClass(tickMark: TickMark): string {
231+
return tickMark === TickMark.ACTIVE
232+
? 'mdc-slider__tick-mark--active'
233+
: 'mdc-slider__tick-mark--inactive';
234+
}
235+
236+
/** Returns an array of the thumb types that exist on the current slider instance. */
237+
_getThumbTypes(): Thumb[] {
238+
return this._isRange() ? [Thumb.START, Thumb.END] : [Thumb.END];
239+
}
240+
241+
static ngAcceptInputType_disabled: BooleanInput;
242+
static ngAcceptInputType_discrete: BooleanInput;
243+
static ngAcceptInputType_showTickMarks: BooleanInput;
244+
static ngAcceptInputType_min: NumberInput;
245+
static ngAcceptInputType_max: NumberInput;
246+
static ngAcceptInputType_step: NumberInput;
34247
}

src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ <h2>MDC slide-toggle</h2>
104104
<mat-slide-toggle>with a label</mat-slide-toggle>
105105

106106
<h2>MDC Slider</h2>
107-
<mat-slider></mat-slider>
108-
<mat-slider value="50"></mat-slider>
109-
<mat-slider tickInterval="1" min="1" max="10" value="5" thumbLabel></mat-slider>
110-
<mat-slider disabled></mat-slider>
111107

112108
<h2>MDC Tabs</h2>
113109

0 commit comments

Comments
 (0)