Skip to content

Commit 390b5f0

Browse files
committed
feat(material-experimental/mdc-slider): implement the SliderAdapter
* complete the core logic for MatSliderThumb and MatSlider
1 parent f8622fe commit 390b5f0

File tree

3 files changed

+194
-101
lines changed

3 files changed

+194
-101
lines changed

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

Lines changed: 104 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,131 +8,146 @@
88

99
import {SpecificEventListener, EventType} from '@material/base';
1010
import {MDCSliderAdapter, Thumb, TickMark} from '@material/slider';
11+
import {MatSlider} from './slider';
1112

1213
export class SliderAdapter implements MDCSliderAdapter {
13-
hasClass(className: string): boolean {
14-
throw Error('Method not implemented.');
14+
constructor(private readonly _delegate: MatSlider) {}
15+
hasClass = (className: string): boolean => {
16+
return this._delegate._hostElement.classList.contains(className);
1517
}
16-
addClass(className: string): void {
17-
throw Error('Method not implemented.');
18+
addClass = (className: string): void => {
19+
this._delegate._hostElement.classList.add(className);
1820
}
19-
removeClass(className: string): void {
20-
throw Error('Method not implemented.');
21+
removeClass = (className: string): void => {
22+
this._delegate._hostElement.classList.remove(className);
2123
}
22-
getAttribute(attribute: string): string | null {
23-
throw Error('Method not implemented.');
24+
getAttribute = (attribute: string): string | null => {
25+
return this._delegate._hostElement.getAttribute(attribute);
2426
}
25-
addThumbClass(className: string, thumb: Thumb): void {
26-
throw Error('Method not implemented.');
27+
addThumbClass = (className: string, thumb: Thumb): void => {
28+
this._delegate._getThumbElement(thumb).classList.add(className);
2729
}
28-
removeThumbClass(className: string, thumb: Thumb): void {
29-
throw Error('Method not implemented.');
30+
removeThumbClass = (className: string, thumb: Thumb): void => {
31+
this._delegate._getThumbElement(thumb).classList.remove(className);
3032
}
31-
getInputValue(thumb: Thumb): string {
32-
throw Error('Method not implemented.');
33+
getInputValue = (thumb: Thumb): string => {
34+
return this._delegate._getInputElement(thumb).value;
3335
}
34-
setInputValue(value: string, thumb: Thumb): void {
35-
throw Error('Method not implemented.');
36+
setInputValue = (value: string, thumb: Thumb): void => {
37+
this._delegate._getInputElement(thumb).value = value;
3638
}
37-
getInputAttribute(attribute: string, thumb: Thumb): string | null {
38-
throw Error('Method not implemented.');
39+
getInputAttribute = (attribute: string, thumb: Thumb): string | null => {
40+
return this._delegate._getInputElement(thumb).getAttribute(attribute);
3941
}
40-
setInputAttribute(attribute: string, value: string, thumb: Thumb): void {
41-
throw Error('Method not implemented.');
42+
setInputAttribute = (attribute: string, value: string, thumb: Thumb): void => {
43+
this._delegate._getInputElement(thumb).setAttribute(attribute, value);
4244
}
43-
removeInputAttribute(attribute: string, thumb: Thumb): void {
44-
throw Error('Method not implemented.');
45+
removeInputAttribute = (attribute: string, thumb: Thumb): void => {
46+
this._delegate._getInputElement(thumb).removeAttribute(attribute);
4547
}
46-
focusInput(thumb: Thumb): void {
47-
throw Error('Method not implemented.');
48+
focusInput = (thumb: Thumb): void => {
49+
this._delegate._getInputElement(thumb).focus();
4850
}
49-
isInputFocused(thumb: Thumb): boolean {
50-
throw Error('Method not implemented.');
51+
isInputFocused = (thumb: Thumb): boolean => {
52+
return this._delegate._getInput(thumb)._isFocused();
5153
}
52-
getThumbKnobWidth(thumb: Thumb): number {
53-
throw Error('Method not implemented.');
54+
getThumbKnobWidth = (thumb: Thumb): number => {
55+
return this._delegate._getKnobElement(thumb).getBoundingClientRect().width;
5456
}
55-
getThumbBoundingClientRect(thumb: Thumb): ClientRect {
56-
throw Error('Method not implemented.');
57+
getThumbBoundingClientRect = (thumb: Thumb): ClientRect => {
58+
return this._delegate._getThumbElement(thumb).getBoundingClientRect();
5759
}
58-
getBoundingClientRect(): ClientRect {
59-
throw Error('Method not implemented.');
60+
getBoundingClientRect = (): ClientRect => {
61+
return this._delegate._hostElement.getBoundingClientRect();
6062
}
61-
isRTL(): boolean {
62-
throw Error('Method not implemented.');
63+
isRTL = (): boolean => {
64+
// TODO(wagnermaciel): Actually implementing this.
65+
// throw Error('Method not implemented.');
66+
return false;
6367
}
64-
setThumbStyleProperty(propertyName: string, value: string, thumb: Thumb): void {
65-
throw Error('Method not implemented.');
68+
setThumbStyleProperty = (propertyName: string, value: string, thumb: Thumb): void => {
69+
this._delegate._getThumbElement(thumb).style.setProperty(propertyName, value);
6670
}
67-
removeThumbStyleProperty(propertyName: string, thumb: Thumb): void {
68-
throw Error('Method not implemented.');
71+
removeThumbStyleProperty = (propertyName: string, thumb: Thumb): void => {
72+
this._delegate._getThumbElement(thumb).style.removeProperty(propertyName);
6973
}
70-
setTrackActiveStyleProperty(propertyName: string, value: string): void {
71-
throw Error('Method not implemented.');
74+
setTrackActiveStyleProperty = (propertyName: string, value: string): void => {
75+
this._delegate._trackActive.nativeElement.style.setProperty(propertyName, value);
7276
}
73-
removeTrackActiveStyleProperty(propertyName: string): void {
74-
throw Error('Method not implemented.');
77+
removeTrackActiveStyleProperty = (propertyName: string): void => {
78+
this._delegate._trackActive.nativeElement.style.removeProperty(propertyName);
7579
}
76-
setValueIndicatorText(value: number, thumb: Thumb): void {
77-
throw Error('Method not implemented.');
80+
setValueIndicatorText = (value: number, thumb: Thumb): void => {
81+
this._delegate._setValueIndicatorText(value, thumb);
7882
}
79-
getValueToAriaValueTextFn(): ((value: number) => string) | null {
80-
throw Error('Method not implemented.');
83+
getValueToAriaValueTextFn = (): ((value: number) => string) | null => {
84+
return this._delegate.displayWith;
8185
}
82-
updateTickMarks(tickMarks: TickMark[]): void {
83-
throw Error('Method not implemented.');
86+
updateTickMarks = (tickMarks: TickMark[]): void => {
87+
this._delegate._tickMarks = tickMarks;
88+
this._delegate._cdr.markForCheck();
8489
}
85-
setPointerCapture(pointerId: number): void {
86-
throw Error('Method not implemented.');
90+
setPointerCapture = (pointerId: number): void => {
91+
this._delegate._hostElement.setPointerCapture(pointerId);
8792
}
88-
emitChangeEvent(value: number, thumb: Thumb): void {
89-
throw Error('Method not implemented.');
93+
emitChangeEvent = (value: number, thumb: Thumb): void => {};
94+
emitInputEvent = (value: number, thumb: Thumb): void => {};
95+
emitDragStartEvent = (value: number, thumb: Thumb): void => {
96+
const input = this._delegate._getInput(thumb);
97+
input.dragStart.emit({
98+
source: input,
99+
parent: this._delegate,
100+
value,
101+
thumb,
102+
});
90103
}
91-
emitInputEvent(value: number, thumb: Thumb): void {
92-
throw Error('Method not implemented.');
104+
emitDragEndEvent = (value: number, thumb: Thumb): void => {
105+
const input = this._delegate._getInput(thumb);
106+
input.dragEnd.emit({
107+
source: input,
108+
parent: this._delegate,
109+
value,
110+
thumb,
111+
});
93112
}
94-
emitDragStartEvent(value: number, thumb: Thumb): void {
95-
throw Error('Method not implemented.');
113+
registerEventHandler =
114+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
115+
this._delegate._hostElement.addEventListener(evtType, handler);
96116
}
97-
emitDragEndEvent(value: number, thumb: Thumb): void {
98-
throw Error('Method not implemented.');
117+
deregisterEventHandler =
118+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
119+
this._delegate._hostElement.removeEventListener(evtType, handler);
99120
}
100-
registerEventHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void {
101-
throw Error('Method not implemented.');
121+
registerThumbEventHandler =
122+
<K extends EventType>(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void => {
123+
this._delegate._getThumbElement(thumb).addEventListener(evtType, handler);
102124
}
103-
deregisterEventHandler<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void {
104-
throw Error('Method not implemented.');
125+
deregisterThumbEventHandler =
126+
<K extends EventType>(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void => {
127+
this._delegate._getThumbElement(thumb).removeEventListener(evtType, handler);
105128
}
106-
registerThumbEventHandler<K extends EventType>
107-
(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void {
108-
throw Error('Method not implemented.');
129+
registerInputEventHandler =
130+
<K extends EventType>(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void => {
131+
this._delegate._getInputElement(thumb).addEventListener(evtType, handler);
109132
}
110-
deregisterThumbEventHandler<K extends EventType>
111-
(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void {
112-
throw Error('Method not implemented.');
133+
deregisterInputEventHandler =
134+
<K extends EventType>(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void => {
135+
this._delegate._getInputElement(thumb).removeEventListener(evtType, handler);
113136
}
114-
registerInputEventHandler<K extends EventType>
115-
(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void {
116-
throw Error('Method not implemented.');
137+
registerBodyEventHandler =
138+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
139+
this._delegate._document.body.addEventListener(evtType, handler);
117140
}
118-
deregisterInputEventHandler<K extends EventType>
119-
(thumb: Thumb, evtType: K, handler: SpecificEventListener<K>): void {
120-
throw Error('Method not implemented.');
141+
deregisterBodyEventHandler =
142+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
143+
this._delegate._document.body.removeEventListener(evtType, handler);
121144
}
122-
registerBodyEventHandler<K extends EventType>
123-
(evtType: K, handler: SpecificEventListener<K>): void {
124-
throw Error('Method not implemented.');
125-
}
126-
deregisterBodyEventHandler<K extends EventType>
127-
(evtType: K, handler: SpecificEventListener<K>): void {
128-
throw Error('Method not implemented.');
129-
}
130-
registerWindowEventHandler<K extends EventType>
131-
(evtType: K, handler: SpecificEventListener<K>): void {
132-
throw Error('Method not implemented.');
133-
}
134-
deregisterWindowEventHandler<K extends EventType>
135-
(evtType: K, handler: SpecificEventListener<K>): void {
136-
throw Error('Method not implemented.');
145+
registerWindowEventHandler =
146+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
147+
this._delegate._window.addEventListener(evtType, handler);
148+
}
149+
deregisterWindowEventHandler =
150+
<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void => {
151+
this._delegate._window.removeEventListener(evtType, handler);
137152
}
138153
}

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

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,36 @@ export interface MatSliderDragEvent {
4747
}) export class MatSliderThumb {
4848
/** The current value of this slider input. */
4949
@Input()
50-
get value(): number { return this._value; }
51-
set value(v: number) { this._value = v; }
52-
private _value: number;
50+
get value(): number { return Number(this._elementRef.nativeElement.getAttribute('value')); }
51+
set value(v: number) {
52+
this._initialized = true;
53+
54+
// If the foundation has already been initialized, we need to
55+
// relay any value updates to it so that it can update the UI.
56+
if (this._slider._initialized) {
57+
this._slider._setValue(v, this.thumb);
58+
} else {
59+
// Setup for the MDC foundation.
60+
this._elementRef.nativeElement.setAttribute('value', v.toString());
61+
}
62+
}
5363

5464
/** The minimum value that this slider input can have. */
5565
@Input()
56-
get min(): number { return 0; }
66+
get min(): number {
67+
return (this._slider._isRange() && this.thumb === Thumb.END)
68+
? this._slider._getValue(Thumb.START)
69+
: this._slider.min;
70+
}
5771
set min(v: number) { throw Error('Invalid attribute "min" on MatSliderThumb.'); }
5872

5973
/** The maximum value that this slider input can have. */
6074
@Input()
61-
get max(): number { return 100; }
75+
get max(): number {
76+
return (this._slider._isRange() && this.thumb === Thumb.START)
77+
? this._slider._getValue(Thumb.END)
78+
: this._slider.max;
79+
}
6280
set max(v: number) { throw Error('Invalid attribute "max" on MatSliderThumb.'); }
6381

6482
/** The size of each increment between the values of the slider. */
@@ -87,10 +105,44 @@ export interface MatSliderDragEvent {
87105
/** Indicates which slider thumb this input corresponds to. */
88106
thumb: Thumb;
89107

108+
/** Whether the value of this slider thumb input has been set. */
109+
_initialized: boolean = false;
110+
90111
constructor(
91112
@Inject(DOCUMENT) private readonly _document: Document,
92113
readonly _elementRef: ElementRef<HTMLInputElement>,
93-
) {}
114+
private readonly _slider: MatSlider,
115+
) {
116+
// Initializing the min and max in the constructor guarantees that they will be
117+
// defined by the time the value gets set. If the range is not defined before we
118+
// try to set the value, we can run into the issue where the value is outside of
119+
// the default range and get capped to the default min or max.
120+
this._elementRef.nativeElement.min = this._slider.min.toString();
121+
this._elementRef.nativeElement.max = this._slider.max.toString();
122+
}
123+
124+
/**
125+
* Sets up the initial state of the slider thumb input.
126+
*
127+
* This is needed because the slider thumb input is passed in via `ng-content`,
128+
* and therefore has no way of knowing which slider thumb it correspond to.
129+
*/
130+
_init(thumb: Thumb): void {
131+
this.thumb = thumb;
132+
133+
// If the value has not been initialized (i.e. no value was provided from
134+
// the user), determine the default value for the slider based on the given thumb.
135+
if (!this._initialized) {
136+
this.value = (this._slider._isRange() && thumb === Thumb.END)
137+
? this._slider.max
138+
: this._slider.min;
139+
}
140+
141+
// Setup for the MDC foundation.
142+
if (this._slider.disabled) {
143+
this._elementRef.nativeElement.disabled = true;
144+
}
145+
}
94146

95147
/** Returns true if this slider input currently has focus. */
96148
_isFocused(): boolean {

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class MatSlider implements AfterViewInit, OnDestroy {
120120
@Input() displayWith: ((value: number) => string) | null;
121121

122122
/** Instance of the MDC slider foundation for this slider. */
123-
private _foundation = new MDCSliderFoundation(new SliderAdapter());
123+
private _foundation = new MDCSliderFoundation(new SliderAdapter(this));
124124

125125
/** Whether the foundation has been initialized. */
126126
_initialized: boolean = false;
@@ -157,11 +157,13 @@ export class MatSlider implements AfterViewInit, OnDestroy {
157157
}
158158

159159
ngAfterViewInit() {
160-
this._foundation.init();
161-
if (this._platform.isBrowser) {
162-
this._foundation.layout();
163-
}
164-
this._initialized = true;
160+
this._initInputs().then(() => {
161+
this._foundation.init();
162+
if (this._platform.isBrowser) {
163+
this._foundation.layout();
164+
}
165+
this._initialized = true;
166+
});
165167
}
166168

167169
ngOnDestroy() {
@@ -170,6 +172,22 @@ export class MatSlider implements AfterViewInit, OnDestroy {
170172
}
171173
}
172174

175+
/**
176+
* Sets up the initial state of the slider thumb inputs.
177+
*
178+
* The slider thumbs need this extra step because are passed in via `ng-content`,
179+
* and therefore have no way of knowing which slider thumb they correspond to.
180+
*
181+
* This method needs to return a promise in order to avoid throwing an
182+
* ExpressionChangedAfterItHasBeenCheckedError error.
183+
*/
184+
_initInputs(): Promise<void> {
185+
return Promise.resolve().then(() => {
186+
this._inputs.get(0)?._init(this._isRange() ? Thumb.START : Thumb.END);
187+
this._inputs.get(1)?._init(Thumb.END);
188+
});
189+
}
190+
173191
/** Gets the current value of given slider thumb. */
174192
_getValue(thumb: Thumb): number {
175193
return thumb === Thumb.START
@@ -226,6 +244,14 @@ export class MatSlider implements AfterViewInit, OnDestroy {
226244
return this._getValueIndicatorText(this._getValue(thumb));
227245
}
228246

247+
/** Sets the value indicator text of the given thumb with the given value. */
248+
_setValueIndicatorText(value: number, thumb: Thumb) {
249+
const valueIndicatorText = this._getValueIndicatorText(value);
250+
thumb === Thumb.END
251+
? this._endValueIndicatorText = valueIndicatorText
252+
: this._startValueIndicatorText = valueIndicatorText;
253+
}
254+
229255
/** Determines the class name for a HTML element. */
230256
_getTickMarkClass(tickMark: TickMark): string {
231257
return tickMark === TickMark.ACTIVE

0 commit comments

Comments
 (0)