@@ -34,25 +34,6 @@ function inBounds({x, y}: PointerEvent, element?: HTMLElement|null) {
3434 return x >= left && x <= right && y >= top && y <= bottom ;
3535}
3636
37- // parse values like: foo or foo,bar
38- function tupleConverter ( attr : string | null ) {
39- const [ , v , e ] =
40- attr ?. match ( / \s * \[ ? \s * ( [ ^ , ] + ) (?: (?: \s * $ ) | (?: \s * , \s * ( .* ) \s * ) ) / ) ?? [ ] ;
41- return e !== undefined ? [ v , e ] : v ;
42- }
43-
44- function toNumber ( value : string ) {
45- return Number ( value ) || 0 ;
46- }
47-
48- function tupleAsString ( value : unknown | [ unknown , unknown ] ) {
49- return Array . isArray ( value ) ? value . join ( ) : String ( value ?? '' ) ;
50- }
51-
52- function valueConverter ( attr : string | null ) {
53- const value = tupleConverter ( attr ) ;
54- return Array . isArray ( value ) ? value . map ( i => toNumber ( i ) ) : toNumber ( value ) ;
55- }
5637
5738function clamp ( value : number , min : number , max : number ) {
5839 return Math . max ( min , Math . min ( max , value ) ) ;
@@ -98,21 +79,40 @@ export class Slider extends LitElement {
9879 /**
9980 * The slider maximum value
10081 */
101- @property ( { type : Number } ) max = 10 ;
82+ @property ( { type : Number } ) max = 100 ;
83+
84+ /**
85+ * The slider value displayed when range is false.
86+ */
87+ @property ( { type : Number } ) value = 50 ;
88+
89+ /**
90+ * The slider start value displayed when range is true.
91+ */
92+ @property ( { type : Number } ) valueStart = 25 ;
93+
94+ /**
95+ * The slider end value displayed when range is true.
96+ */
97+ @property ( { type : Number } ) valueEnd = 75 ;
98+
99+ /**
100+ * An optional label for the slider's value displayed when range is
101+ * false; if not set, the label is the value itself.
102+ */
103+ @property ( ) valueLabel ?: string | undefined ;
102104
103105 /**
104- * The slider value, can be a single number, or an array tuple indicating
105- * a start and end value .
106+ * An optional label for the slider's start value displayed when
107+ * range is true; if not set, the label is the valueStart itself .
106108 */
107- @property ( { converter : valueConverter } ) value : number | [ number , number ] = 0 ;
109+ @property ( ) valueStartLabel ?: string | undefined ;
108110
109111 /**
110- * An optinoal label for the slider's value; if not set, the label is the
111- * value itself. This can be a string or string tuple when start and end
112- * values are used.
112+ * An optional label for the slider's end value displayed when
113+ * range is true; if not set, the label is the valueEnd itself.
113114 */
114- @property ( { converter : tupleConverter } )
115- valueLabel ?: string | [ string , string ] | undefined ;
115+ @property ( ) valueEndLabel ?: string | undefined ;
116116
117117 /**
118118 * The step between values.
@@ -129,6 +129,13 @@ export class Slider extends LitElement {
129129 */
130130 @property ( { type : Boolean } ) withLabel = false ;
131131
132+ /**
133+ * Whether or not to show a value range. When false, the slider displays
134+ * a slideable handle for the value property; when true, it displays
135+ * slideable handles for the valueStart and valueEnd properties.
136+ */
137+ @property ( { type : Boolean } ) range = false ;
138+
132139 /**
133140 * The HTML name to use in form submission.
134141 */
@@ -141,17 +148,6 @@ export class Slider extends LitElement {
141148 return this . closest ( 'form' ) ;
142149 }
143150
144-
145- /**
146- * Read only computed value representing the fraction between 0 and 1
147- * respresenting the value's position between min and max. This is a
148- * single fraction or a tuple if the value specifies start and end values.
149- */
150- get valueAsFraction ( ) {
151- const { lowerFraction, upperFraction} = this . getMetrics ( ) ;
152- return this . allowRange ? [ lowerFraction , upperFraction ] : upperFraction ;
153- }
154-
155151 private getMetrics ( ) {
156152 const step = Math . max ( this . step , 1 ) ;
157153 const range = Math . max ( this . max - this . min , step ) ;
@@ -210,38 +206,26 @@ export class Slider extends LitElement {
210206 this . inputB ?. focus ( ) ;
211207 }
212208
213- get valueAsString ( ) {
214- return tupleAsString ( this . value ) ;
215- }
216-
217209 // value coerced to a string
218210 [ getFormValue ] ( ) {
219- return this . valueAsString ;
211+ return this . range ? `${ this . valueStart } , ${ this . valueEnd } ` :
212+ `${ this . value } ` ;
220213 }
221214
222- // If range should be allowed (detected via value format).
223- private allowRange = false ;
224-
225215 // indicates input values are crossed over each other from initial rendering.
226216 private isFlipped ( ) {
227217 return this . valueA > this . valueB ;
228218 }
229219
230220 protected override willUpdate ( changed : PropertyValues ) {
231- if ( changed . has ( 'value' ) || changed . has ( 'min' ) || changed . has ( 'max' ) ||
232- changed . has ( 'step' ) ) {
233- this . allowRange = Array . isArray ( this . value ) ;
234- const step = Math . max ( this . step , 1 ) ;
235- let lower =
236- this . allowRange ? ( this . value as [ number , number ] ) [ 0 ] : this . min ;
237- lower = clamp ( lower - ( lower % step ) , this . min , this . max ) ;
238- let upper = this . allowRange ? ( this . value as [ number , number ] ) [ 1 ] :
239- this . value as number ;
240- upper = clamp ( upper - ( upper % step ) , this . min , this . max ) ;
241- const isFlipped = this . isFlipped ( ) && this . allowRange ;
242- this . valueA = isFlipped ? upper : lower ;
243- this . valueB = isFlipped ? lower : upper ;
244- }
221+ const step = Math . max ( this . step , 1 ) ;
222+ let lower = this . range ? this . valueStart : this . min ;
223+ lower = clamp ( lower - ( lower % step ) , this . min , this . max ) ;
224+ let upper = this . range ? this . valueEnd : this . value ;
225+ upper = clamp ( upper - ( upper % step ) , this . min , this . max ) ;
226+ const isFlipped = this . isFlipped ( ) && this . range ;
227+ this . valueA = isFlipped ? upper : lower ;
228+ this . valueB = isFlipped ? lower : upper ;
245229
246230 // manually handle ripple hover state since the handle is pointer events
247231 // none.
@@ -255,7 +239,7 @@ export class Slider extends LitElement {
255239 }
256240
257241 protected override async updated ( changed : PropertyValues ) {
258- if ( changed . has ( 'value ' ) || changed . has ( 'valueA' ) ||
242+ if ( changed . has ( 'range ' ) || changed . has ( 'valueA' ) ||
259243 changed . has ( 'valueB' ) ) {
260244 await this . updateComplete ;
261245 this . handlesOverlapping = isOverlapping ( this . handleA , this . handleB ) ;
@@ -272,14 +256,19 @@ export class Slider extends LitElement {
272256 // for generating tick marks
273257 '--slider-tick-count' : String ( range / step ) ,
274258 } ;
275- const containerClasses = { ranged : this . allowRange } ;
259+ const containerClasses = { ranged : this . range } ;
276260
277261 // optional label values to show in place of the value.
278- const labelA = String ( this . valueLabel ?. [ isFlipped ? 1 : 0 ] ?? this . valueA ) ;
279- const labelB = String (
280- ( this . allowRange ? this . valueLabel ?. [ isFlipped ? 0 : 1 ] :
281- this . valueLabel ) ??
282- this . valueB ) ;
262+ let labelA = String ( this . valueA ) ;
263+ let labelB = String ( this . valueB ) ;
264+ if ( this . range ) {
265+ const a = isFlipped ? this . valueEndLabel : this . valueStartLabel ;
266+ const b = isFlipped ? this . valueStartLabel : this . valueEndLabel ;
267+ labelA = a ?? labelA ;
268+ labelB = b ?? labelB ;
269+ } else {
270+ labelB = this . valueLabel ?? labelB ;
271+ }
283272
284273 const inputAProps = {
285274 id : 'a' ,
@@ -322,13 +311,14 @@ export class Slider extends LitElement {
322311 class ="container ${ classMap ( containerClasses ) } "
323312 style =${ styleMap ( containerStyles ) }
324313 >
325- ${ when ( this . allowRange , ( ) => this . renderInput ( inputAProps ) ) }
314+ ${ when ( this . range , ( ) => this . renderInput ( inputAProps ) ) }
326315 ${ this . renderInput ( inputBProps ) }
327316 ${ this . renderTrack ( ) }
328317 < div class ="handleContainerPadded ">
329318 < div class ="handleContainerBlock ">
330319 < div class ="handleContainer ${ classMap ( handleContainerClasses ) } ">
331- ${ when ( this . allowRange , ( ) => this . renderHandle ( handleAProps ) ) }
320+ ${
321+ when ( this . range , ( ) => this . renderHandle ( handleAProps ) ) }
332322 ${ this . renderHandle ( handleBProps ) }
333323 </ div >
334324 </ div >
@@ -379,7 +369,7 @@ export class Slider extends LitElement {
379369 } ) {
380370 // when ranged, ensure announcement includes value info.
381371 const ariaLabelDescriptor =
382- this . allowRange ? ` - ${ lesser ? `start` : `end` } handle` : '' ;
372+ this . range ? ` - ${ lesser ? `start` : `end` } handle` : '' ;
383373 // Needed for closure conformance
384374 const { ariaLabel} = this as ARIAMixinStrict ;
385375 return html `< input type ="range "
@@ -507,7 +497,12 @@ export class Slider extends LitElement {
507497 // update value only on interaction
508498 const lower = Math . min ( this . valueA , this . valueB ) ;
509499 const upper = Math . max ( this . valueA , this . valueB ) ;
510- this . value = this . allowRange ? [ lower , upper ] : this . valueB ;
500+ if ( this . range ) {
501+ this . valueStart = lower ;
502+ this . valueEnd = upper ;
503+ } else {
504+ this . value = this . valueB ;
505+ }
511506 }
512507
513508 private handleChange ( event : Event ) {
0 commit comments