@@ -106,6 +106,9 @@ export class NumberField extends TextfieldBase {
106
106
107
107
_forcedUnit = '' ;
108
108
109
+ @property ( { type : Boolean , reflect : true } )
110
+ public scrubbing = false ;
111
+
109
112
/**
110
113
* An `<sp-number-field>` element will process its numeric value with
111
114
* `new Intl.NumberFormat(this.resolvedLanguage, this.formatOptions).format(this.valueAsNumber)`
@@ -149,6 +152,9 @@ export class NumberField extends TextfieldBase {
149
152
@property ( { type : Number , reflect : true , attribute : 'step-modifier' } )
150
153
public stepModifier = 10 ;
151
154
155
+ @property ( { type : Number } )
156
+ public stepperpixel ?: number ;
157
+
152
158
@property ( { type : Number } )
153
159
public override set value ( rawValue : number ) {
154
160
const value = this . validateInput ( rawValue ) ;
@@ -242,13 +248,29 @@ export class NumberField extends TextfieldBase {
242
248
private change ! : ( event : PointerEvent ) => void ;
243
249
private safty ! : number ;
244
250
private languageResolver = new LanguageResolutionController ( this ) ;
251
+ private pointerDragXLocation ?: number ;
252
+ private pointerDownTime ?: number ;
253
+ private scrubDistance = 0 ;
245
254
246
255
private handlePointerdown ( event : PointerEvent ) : void {
247
256
if ( event . button !== 0 ) {
248
257
event . preventDefault ( ) ;
249
258
return ;
250
259
}
251
260
this . managedInput = true ;
261
+
262
+ if ( ! this . focused ) {
263
+ this . setPointerCapture ( event . pointerId ) ;
264
+ this . scrub ( event ) ;
265
+ }
266
+ }
267
+
268
+ private handleButtonPointerdown ( event : PointerEvent ) : void {
269
+ if ( event . button !== 0 ) {
270
+ event . preventDefault ( ) ;
271
+ return ;
272
+ }
273
+ this . managedInput = true ;
252
274
this . buttons . setPointerCapture ( event . pointerId ) ;
253
275
const stepUpRect = this . buttons . children [ 0 ] . getBoundingClientRect ( ) ;
254
276
const stepDownRect = this . buttons . children [ 1 ] . getBoundingClientRect ( ) ;
@@ -287,11 +309,11 @@ export class NumberField extends TextfieldBase {
287
309
this . change ( event ) ;
288
310
}
289
311
290
- private handlePointermove ( event : PointerEvent ) : void {
312
+ private handleButtonPointermove ( event : PointerEvent ) : void {
291
313
this . findChange ( event ) ;
292
314
}
293
315
294
- private handlePointerup ( event : PointerEvent ) : void {
316
+ private handleButtonPointerup ( event : PointerEvent ) : void {
295
317
this . buttons . releasePointerCapture ( event . pointerId ) ;
296
318
cancelAnimationFrame ( this . nextChange ) ;
297
319
clearTimeout ( this . safty ) ;
@@ -340,6 +362,81 @@ export class NumberField extends TextfieldBase {
340
362
this . stepBy ( - 1 * factor ) ;
341
363
}
342
364
365
+ private handlePointermove ( event : PointerEvent ) : void {
366
+ this . scrub ( event ) ;
367
+ }
368
+
369
+ private handlePointerup ( event : PointerEvent ) : void {
370
+ this . releasePointerCapture ( event . pointerId ) ;
371
+ this . scrub ( event ) ;
372
+ cancelAnimationFrame ( this . nextChange ) ;
373
+ clearTimeout ( this . safty ) ;
374
+ this . managedInput = false ;
375
+ this . setValue ( ) ;
376
+ }
377
+
378
+ private scrub ( event : PointerEvent ) : void {
379
+ switch ( event . type ) {
380
+ case 'pointerdown' :
381
+ this . scrubbing = true ;
382
+ this . pointerDragXLocation = event . clientX ;
383
+ this . pointerDownTime = Date . now ( ) ;
384
+ this . inputElement . disabled = true ;
385
+ this . addEventListener ( 'pointermove' , this . handlePointermove ) ;
386
+ this . addEventListener ( 'pointerup' , this . handlePointerup ) ;
387
+ this . addEventListener ( 'pointercancel' , this . handlePointerup ) ;
388
+ event . preventDefault ( ) ;
389
+ break ;
390
+
391
+ case 'pointermove' :
392
+ if (
393
+ this . pointerDragXLocation &&
394
+ this . pointerDownTime &&
395
+ Date . now ( ) - this . pointerDownTime > 250
396
+ ) {
397
+ const amtPerPixel = this . stepperpixel || this . _step ;
398
+ const dist : number =
399
+ event . clientX - this . pointerDragXLocation ;
400
+ const delta =
401
+ Math . round ( dist * amtPerPixel ) *
402
+ ( event . shiftKey ? this . stepModifier : 1 ) ;
403
+ this . scrubDistance += Math . abs ( dist ) ;
404
+ this . pointerDragXLocation = event . clientX ;
405
+ this . stepBy ( delta ) ;
406
+ event . preventDefault ( ) ;
407
+ }
408
+ break ;
409
+
410
+ default :
411
+ this . pointerDragXLocation = undefined ;
412
+ this . scrubbing = false ;
413
+ this . inputElement . disabled = false ;
414
+ this . removeEventListener ( 'pointermove' , this . handlePointermove ) ;
415
+ this . removeEventListener ( 'pointerup' , this . handlePointerup ) ;
416
+ this . removeEventListener ( 'pointercancel' , this . handlePointerup ) ;
417
+
418
+ // if user has scrubbed, disallow focus of field
419
+ const bounds = this . getBoundingClientRect ( ) ;
420
+ if (
421
+ this . scrubDistance > 0 &&
422
+ this . pointerDownTime &&
423
+ Date . now ( ) - this . pointerDownTime > 250
424
+ ) {
425
+ event . preventDefault ( ) ;
426
+ } else if (
427
+ event . clientX >= bounds . x &&
428
+ event . clientX <= bounds . x + bounds . width &&
429
+ event . clientY >= bounds . y &&
430
+ event . clientY <= bounds . y + bounds . height
431
+ ) {
432
+ this . focus ( ) ;
433
+ }
434
+ this . scrubDistance = 0 ;
435
+ this . pointerDownTime = undefined ;
436
+ break ;
437
+ }
438
+ }
439
+
343
440
private handleKeydown ( event : KeyboardEvent ) : void {
344
441
if ( this . isComposing ) return ;
345
442
switch ( event . code ) {
@@ -375,6 +472,9 @@ export class NumberField extends TextfieldBase {
375
472
}
376
473
377
474
protected override onFocus ( ) : void {
475
+ if ( this . pointerDragXLocation ) {
476
+ return ;
477
+ }
378
478
super . onFocus ( ) ;
379
479
this . _trackingValue = this . inputValue ;
380
480
this . keyboardFocused = ! this . readonly && true ;
@@ -639,7 +739,10 @@ export class NumberField extends TextfieldBase {
639
739
@focusin =${ this . handleFocusin }
640
740
@focusout =${ this . handleFocusout }
641
741
${ streamingListener ( {
642
- start : [ 'pointerdown' , this . handlePointerdown ] ,
742
+ start : [
743
+ 'pointerdown' ,
744
+ this . handleButtonPointerdown ,
745
+ ] ,
643
746
streamInside : [
644
747
[
645
748
'pointermove' ,
@@ -648,15 +751,15 @@ export class NumberField extends TextfieldBase {
648
751
'pointerover' ,
649
752
'pointerout' ,
650
753
] ,
651
- this . handlePointermove ,
754
+ this . handleButtonPointermove ,
652
755
] ,
653
756
end : [
654
757
[
655
758
'pointerup' ,
656
759
'pointercancel' ,
657
760
'pointerleave' ,
658
761
] ,
659
- this . handlePointerup ,
762
+ this . handleButtonPointerup ,
660
763
] ,
661
764
} ) }
662
765
>
@@ -729,6 +832,7 @@ export class NumberField extends TextfieldBase {
729
832
this . addEventListener ( 'keydown' , this . handleKeydown ) ;
730
833
this . addEventListener ( 'compositionstart' , this . handleCompositionStart ) ;
731
834
this . addEventListener ( 'compositionend' , this . handleCompositionEnd ) ;
835
+ this . addEventListener ( 'pointerdown' , this . handlePointerdown ) ;
732
836
}
733
837
734
838
protected override updated ( changes : PropertyValues < this> ) : void {
0 commit comments