@@ -13,7 +13,36 @@ import { fromEvent, Subscription } from "rxjs";
1313import { ControlValueAccessor , NG_VALUE_ACCESSOR } from "@angular/forms" ;
1414
1515/**
16+ * Used to select from ranges of values. [See here](https://www.carbondesignsystem.com/components/slider/usage) for usage information.
1617 *
18+ * The simpelist possible slider usage looks something like:
19+ * ```html
20+ * <ibm-slider></ibm-slider>
21+ * ```
22+ *
23+ * That will render a slider without labels or alternative value input. Labels can be provided by
24+ * elements with `[minLabel]` and `[maxLabel]` attributes, and an `input` (may use the `ibmInput` directive) can be supplied
25+ * for use as an alternative value field.
26+ *
27+ * ex:
28+ * ```html
29+ * <!-- full example -->
30+ * <ibm-slider>
31+ * <span minLabel>0GB</span>
32+ * <span maxLabel>100GB</span>
33+ * <input/>
34+ * </ibm-slider>
35+ * <!-- with just an input -->
36+ * <ibm-slider>
37+ * <input/>
38+ * </ibm-slider>
39+ * <!-- with just one label -->
40+ * <ibm-slider>
41+ * <span maxLabel>Maximum</span>
42+ * </ibm-slider>
43+ * ```
44+ *
45+ * Slider supports `NgModel` by default, as well as two way binding to the `value` input.
1746 */
1847@Component ( {
1948 selector : "ibm-slider" ,
@@ -63,11 +92,15 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
6392 ]
6493} )
6594export class Slider implements AfterViewInit , OnDestroy , ControlValueAccessor {
95+ /** Used to generate unique IDs */
6696 private static count = 0 ;
67-
97+ /** The lower bound of our range */
6898 @Input ( ) min = 0 ;
99+ /** The upper bound of our range */
69100 @Input ( ) max = 100 ;
101+ /** The interval for our range */
70102 @Input ( ) step = 1 ;
103+ /** Set the inital value. Avliable for two way binding */
71104 @Input ( ) set value ( v ) {
72105 if ( v > this . max ) {
73106 v = this . max ;
@@ -91,8 +124,14 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
91124 get value ( ) {
92125 return this . _value ;
93126 }
127+ /** Base ID for the slider. The min and max labels get IDs `${this.id}-bottom-range` and `${this.id}-top-range` respectivly */
94128 @Input ( ) id = `slider-${ Slider . count ++ } ` ;
129+ /** Value used to "multiply" the `step` when using arrow keys to select values */
95130 @Input ( ) shiftMultiplier = 4 ;
131+ /** Disables the range visually and functionally */
132+ // TODO: implement disabled state
133+ @Input ( ) disabled = false ;
134+ /** Emits every time a new value is selected */
96135 @Output ( ) valueChange : EventEmitter < number > = new EventEmitter ( ) ;
97136 @HostBinding ( "class.bx--slider-container" ) hostClass = true ;
98137 @ViewChild ( "thumb" ) thumb : ElementRef ;
@@ -103,6 +142,7 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
103142 public topRangeId = `${ this . id } -top-range` ;
104143
105144 protected isMouseDown = false ;
145+ /** Array of event subscriptions so we can batch unsubscribe in `ngOnDestroy` */
106146 protected eventSubscriptions : Array < Subscription > = [ ] ;
107147 protected slidAmount = 0 ;
108148 protected input ;
@@ -111,9 +151,12 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
111151 constructor ( protected elementRef : ElementRef ) { }
112152
113153 ngAfterViewInit ( ) {
154+ // bind mousemove and mouseup to the document so we don't have issues tracking the mouse
114155 this . eventSubscriptions . push ( fromEvent ( document , "mousemove" ) . subscribe ( this . onMouseMove . bind ( this ) ) ) ;
115156 this . eventSubscriptions . push ( fromEvent ( document , "mouseup" ) . subscribe ( this . onMouseUp . bind ( this ) ) ) ;
116157
158+ // ODO: ontouchstart/ontouchmove/ontouchend
159+
117160 // set up the optional input
118161 this . input = this . elementRef . nativeElement . querySelector ( "input:not([type=range])" ) ;
119162 if ( this . input ) {
@@ -122,42 +165,52 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
122165 this . input . classList . add ( "bx--text-input" ) ;
123166 this . input . setAttribute ( "aria-labelledby" , `${ this . bottomRangeId } ${ this . topRangeId } ` ) ;
124167 this . input . value = this . value ;
168+ // bind events on our optional input
125169 this . eventSubscriptions . push ( fromEvent ( this . input , "change" ) . subscribe ( this . onChange . bind ( this ) ) ) ;
126170 this . eventSubscriptions . push ( fromEvent ( this . input , "focus" ) . subscribe ( this . onFocus . bind ( this ) ) ) ;
127171 }
128172 }
129173
174+ /** Clean up our DOMEvent subscriptions */
130175 ngOnDestroy ( ) {
131176 this . eventSubscriptions . forEach ( subscription => {
132177 subscription . unsubscribe ( ) ;
133178 } ) ;
134179 }
135180
181+ /** Send changes back to the model */
136182 propagateChange = ( _ : any ) => { } ;
137183
184+ /** Register a change propagation function for `ControlValueAccessor` */
138185 registerOnChange ( fn : any ) {
139186 this . propagateChange = fn ;
140187 }
141188
189+ /** Callback to notify the model when our input has been touched */
142190 onTouched : ( ) => any = ( ) => { } ;
143191
192+ /** Register a callback to notify when our input has been touched */
144193 registerOnTouched ( fn : any ) {
145194 this . onTouched = fn ;
146195 }
147196
197+ /** Recives a value from the model */
148198 writeValue ( v : any ) {
149199 this . value = v ;
150200 }
151201
202+ /** Returns the amount of "completeness" as a fraction of the total track width */
152203 getPercentComplete ( ) {
153204 const trackWidth = this . track . nativeElement . getBoundingClientRect ( ) . width ;
154205 return this . slidAmount / trackWidth ;
155206 }
156207
208+ /** Helper function to return the CSS transform `scaleX` function */
157209 scaleX ( complete ) {
158210 return `scaleX(${ complete } )` ;
159211 }
160212
213+ /** Converts a given px value to a "real" value in our range */
161214 convertToValue ( pxAmount ) {
162215 // basic concept borrowed from carbon-components
163216 // ref: https://github.com/IBM/carbon-components/blob/43bf3abdc2f8bdaa38aa84e0f733adde1e1e8894/src/components/slider/slider.js#L147-L151
@@ -168,6 +221,7 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
168221 return rounded + this . min ;
169222 }
170223
224+ /** Converts a given "real" value to a px value we can update the view with */
171225 convertToPx ( value ) {
172226 const trackWidth = this . track . nativeElement . getBoundingClientRect ( ) . width ;
173227 if ( value >= this . max ) {
@@ -181,28 +235,42 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
181235 return Math . round ( trackWidth * ( value / this . max ) ) ;
182236 }
183237
238+ /**
239+ * Increments the value by the step value, or the step value multiplied by the `multiplier` argument.
240+ *
241+ * @argument multiplier Defaults to `1`, multiplied with the step value.
242+ */
184243 incrementValue ( multiplier = 1 ) {
185244 this . value = this . value + ( this . step * multiplier ) ;
186245 }
187246
247+ /**
248+ * Decrements the value by the step value, or the step value multiplied by the `multiplier` argument.
249+ *
250+ * @argument multiplier Defaults to `1`, multiplied with the step value.
251+ */
188252 decrementValue ( multiplier = 1 ) {
189253 this . value = this . value - ( this . step * multiplier ) ;
190254 }
191255
256+ /** Change handler for the optional input */
192257 onChange ( event ) {
193258 this . value = event . target . value ;
194259 }
195260
261+ /** Handles clicks on the range track, and setting the value to it's "real" equivilent */
196262 onClick ( event ) {
197263 const trackLeft = this . track . nativeElement . getBoundingClientRect ( ) . left ;
198264 this . value = this . convertToValue ( event . clientX - trackLeft ) ;
199265 console . log ( event ) ;
200266 }
201267
268+ /** Focus handler for the optional input */
202269 onFocus ( { target} ) {
203270 target . select ( ) ;
204271 }
205272
273+ /** Mouse move handler. Responsible for updating the value and visual selection based on mouse movement */
206274 onMouseMove ( event ) {
207275 if ( this . isMouseDown ) {
208276 const trackWidth = this . track . nativeElement . getBoundingClientRect ( ) . width ;
@@ -217,16 +285,19 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
217285 }
218286 }
219287
288+ /** Enables the `onMouseMove` handler */
220289 onMouseDown ( event ) {
221290 event . preventDefault ( ) ;
222291 this . thumb . nativeElement . focus ( ) ;
223292 this . isMouseDown = true ;
224293 }
225294
295+ /** Disables the `onMouseMove` handler */
226296 onMouseUp ( ) {
227297 this . isMouseDown = false ;
228298 }
229299
300+ /** Calls `incrementValue` for ArrowRight and ArrowUp, `decrementValue` for ArrowLeft and ArrowDown */
230301 onKeyDown ( event : KeyboardEvent ) {
231302 event . preventDefault ( ) ;
232303 const multiplier = event . shiftKey ? this . shiftMultiplier : 1 ;
0 commit comments