@@ -46,33 +46,56 @@ export const SliderMixin = (superClass) =>
4646 constructor ( ) {
4747 super ( ) ;
4848
49- this . __thumbIndex = 0 ;
49+ this . __onPointerMove = this . __onPointerMove . bind ( this ) ;
50+ this . __onPointerUp = this . __onPointerUp . bind ( this ) ;
51+
52+ // Use separate mousedown listener for focusing the input, as
53+ // pointerdown fires too early and the global `keyboardActive`
54+ // flag isn't updated yet, which incorrectly shows focus-ring
55+ this . addEventListener ( 'mousedown' , ( e ) => this . __onMouseDown ( e ) ) ;
56+ this . addEventListener ( 'pointerdown' , ( e ) => this . __onPointerDown ( e ) ) ;
57+ }
58+
59+ /** @protected */
60+ firstUpdated ( ) {
61+ super . firstUpdated ( ) ;
62+
63+ this . __lastCommittedValue = this . value ;
64+ }
65+
66+ /**
67+ * @param {Event } event
68+ * @return {number }
69+ */
70+ __getThumbIndex ( _event ) {
71+ return 0 ;
5072 }
5173
5274 /**
5375 * @param {number } value
5476 * @param {number } index
77+ * @param {number[] } fullValue
5578 * @private
5679 */
57- __updateValue ( value , index = this . __thumbIndex ) {
80+ __updateValue ( value , index , fullValue = this . __value ) {
5881 const { min, max, step } = this . __getConstraints ( ) ;
5982
60- const minValue = this . __value [ index - 1 ] || min ;
61- const maxValue = this . __value [ index + 1 ] || max ;
83+ const minValue = fullValue [ index - 1 ] !== undefined ? fullValue [ index - 1 ] : min ;
84+ const maxValue = fullValue [ index + 1 ] !== undefined ? fullValue [ index + 1 ] : max ;
6285
6386 const safeValue = Math . min ( Math . max ( value , minValue ) , maxValue ) ;
6487
65- const offset = safeValue - min ;
88+ const offset = safeValue - minValue ;
6689 const nearestOffset = Math . round ( offset / step ) * step ;
67- const nearestValue = min + nearestOffset ;
90+ const nearestValue = minValue + nearestOffset ;
6891
6992 const newValue = Math . round ( nearestValue ) ;
7093
71- this . __value = this . __value . with ( index , newValue ) ;
94+ this . __value = fullValue . with ( index , newValue ) ;
7295 }
7396
7497 /**
75- * @return {{ min: number, max: number} }
98+ * @return {{ min: number, max: number, step: number } }
7699 * @private
77100 */
78101 __getConstraints ( ) {
@@ -93,24 +116,141 @@ export const SliderMixin = (superClass) =>
93116 return ( 100 * ( value - min ) ) / ( max - min ) ;
94117 }
95118
119+ /**
120+ * @param {number } percent
121+ * @return {number }
122+ * @private
123+ */
124+ __getValueFromPercent ( percent ) {
125+ const { min, max } = this . __getConstraints ( ) ;
126+ return min + percent * ( max - min ) ;
127+ }
128+
129+ /**
130+ * @param {PointerEvent } event
131+ * @return {number }
132+ * @private
133+ */
134+ __getEventPercent ( event ) {
135+ const offset = event . offsetX ;
136+ const size = this . offsetWidth ;
137+ const safeOffset = Math . min ( Math . max ( offset , 0 ) , size ) ;
138+ return safeOffset / size ;
139+ }
140+
141+ /**
142+ * @param {PointerEvent } event
143+ * @return {number }
144+ * @private
145+ */
146+ __getEventValue ( event ) {
147+ const percent = this . __getEventPercent ( event ) ;
148+ return this . __getValueFromPercent ( percent ) ;
149+ }
150+
151+ /**
152+ * @param {PointerEvent } event
153+ * @private
154+ */
155+ __onMouseDown ( event ) {
156+ const part = event . composedPath ( ) [ 0 ] . getAttribute ( 'part' ) ;
157+ if ( ! part || ( ! part . startsWith ( 'track' ) && ! part . startsWith ( 'thumb' ) ) ) {
158+ return ;
159+ }
160+
161+ // Prevent losing focus
162+ event . preventDefault ( ) ;
163+
164+ this . __focusInput ( event ) ;
165+ }
166+
167+ /**
168+ * @param {PointerEvent } event
169+ * @private
170+ */
171+ __onPointerDown ( event ) {
172+ if ( event . button !== 0 ) {
173+ return ;
174+ }
175+
176+ const part = event . composedPath ( ) [ 0 ] . getAttribute ( 'part' ) ;
177+ if ( ! part || ( ! part . startsWith ( 'track' ) && ! part . startsWith ( 'thumb' ) ) ) {
178+ return ;
179+ }
180+
181+ this . setPointerCapture ( event . pointerId ) ;
182+ this . addEventListener ( 'pointermove' , this . __onPointerMove ) ;
183+ this . addEventListener ( 'pointerup' , this . __onPointerUp ) ;
184+ this . addEventListener ( 'pointercancel' , this . __onPointerUp ) ;
185+
186+ this . __thumbIndex = this . __getThumbIndex ( event ) ;
187+
188+ // Update value on track click
189+ if ( part . startsWith ( 'track' ) ) {
190+ const newValue = this . __getEventValue ( event ) ;
191+ this . __updateValue ( newValue , this . __thumbIndex ) ;
192+ this . __commitValue ( ) ;
193+ }
194+ }
195+
196+ /**
197+ * @param {PointerEvent } event
198+ * @private
199+ */
200+ __onPointerMove ( event ) {
201+ const newValue = this . __getEventValue ( event ) ;
202+ this . __updateValue ( newValue , this . __thumbIndex ) ;
203+ this . __commitValue ( ) ;
204+ }
205+
206+ /**
207+ * @param {PointerEvent } event
208+ * @private
209+ */
210+ __onPointerUp ( event ) {
211+ this . __thumbIndex = null ;
212+
213+ this . releasePointerCapture ( event . pointerId ) ;
214+ this . removeEventListener ( 'pointermove' , this . __onPointerMove ) ;
215+ this . removeEventListener ( 'pointerup' , this . __onPointerUp ) ;
216+ this . removeEventListener ( 'pointercancel' , this . __onPointerUp ) ;
217+
218+ this . __detectAndDispatchChange ( ) ;
219+ }
220+
221+ /**
222+ * @param {Event } event
223+ * @private
224+ */
225+ __focusInput ( _event ) {
226+ this . focus ( { focusVisible : false } ) ;
227+ }
228+
96229 /** @private */
97230 __detectAndDispatchChange ( ) {
98- if ( this . __lastCommittedValue !== this . value ) {
231+ if ( JSON . stringify ( this . __lastCommittedValue ) !== JSON . stringify ( this . value ) ) {
99232 this . __lastCommittedValue = this . value ;
100233 this . dispatchEvent ( new Event ( 'change' , { bubbles : true } ) ) ;
101234 }
102235 }
103236
104- /** @private */
237+ /**
238+ * @param {Event } event
239+ * @private
240+ */
105241 __onInput ( event ) {
106- this . __updateValue ( event . target . value ) ;
242+ const index = this . __getThumbIndex ( event ) ;
243+ this . __updateValue ( event . target . value , index ) ;
107244 this . __commitValue ( ) ;
108- this . __detectAndDispatchChange ( ) ;
109245 }
110246
111- /** @private */
247+ /**
248+ * @param {Event } event
249+ * @private
250+ */
112251 __onChange ( event ) {
113252 event . stopPropagation ( ) ;
253+ this . __detectAndDispatchChange ( ) ;
114254 }
115255
116256 /**
0 commit comments