@@ -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,31 @@ 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
+ this . shiftPressed = event . shiftKey ;
262
+
263
+ if ( ! this . focused ) {
264
+ this . setPointerCapture ( event . pointerId ) ;
265
+ this . scrub ( event ) ;
266
+ }
267
+ }
268
+
269
+ private handleButtonPointerdown ( event : PointerEvent ) : void {
270
+ if ( event . button !== 0 ) {
271
+ event . preventDefault ( ) ;
272
+ return ;
273
+ }
274
+ this . managedInput = true ;
275
+ this . shiftPressed = event . shiftKey ;
252
276
this . buttons . setPointerCapture ( event . pointerId ) ;
253
277
const stepUpRect = this . buttons . children [ 0 ] . getBoundingClientRect ( ) ;
254
278
const stepDownRect = this . buttons . children [ 1 ] . getBoundingClientRect ( ) ;
@@ -287,11 +311,11 @@ export class NumberField extends TextfieldBase {
287
311
this . change ( event ) ;
288
312
}
289
313
290
- private handlePointermove ( event : PointerEvent ) : void {
314
+ private handleButtonPointermove ( event : PointerEvent ) : void {
291
315
this . findChange ( event ) ;
292
316
}
293
317
294
- private handlePointerup ( event : PointerEvent ) : void {
318
+ private handleButtonPointerup ( event : PointerEvent ) : void {
295
319
this . buttons . releasePointerCapture ( event . pointerId ) ;
296
320
cancelAnimationFrame ( this . nextChange ) ;
297
321
clearTimeout ( this . safty ) ;
@@ -340,8 +364,84 @@ export class NumberField extends TextfieldBase {
340
364
this . stepBy ( - 1 * factor ) ;
341
365
}
342
366
367
+ private handlePointermove ( event : PointerEvent ) : void {
368
+ this . scrub ( event ) ;
369
+ }
370
+
371
+ private handlePointerup ( event : PointerEvent ) : void {
372
+ this . releasePointerCapture ( event . pointerId ) ;
373
+ this . scrub ( event ) ;
374
+ cancelAnimationFrame ( this . nextChange ) ;
375
+ clearTimeout ( this . safty ) ;
376
+ this . managedInput = false ;
377
+ this . setValue ( ) ;
378
+ }
379
+
380
+ private scrub ( event : PointerEvent ) : void {
381
+ switch ( event . type ) {
382
+ case 'pointerdown' :
383
+ this . scrubbing = true ;
384
+ this . pointerDragXLocation = event . clientX ;
385
+ this . pointerDownTime = Date . now ( ) ;
386
+ this . inputElement . disabled = true ;
387
+ this . addEventListener ( 'pointermove' , this . handlePointermove ) ;
388
+ this . addEventListener ( 'pointerup' , this . handlePointerup ) ;
389
+ this . addEventListener ( 'pointercancel' , this . handlePointerup ) ;
390
+ event . preventDefault ( ) ;
391
+ break ;
392
+
393
+ case 'pointermove' :
394
+ if (
395
+ this . pointerDragXLocation &&
396
+ this . pointerDownTime &&
397
+ Date . now ( ) - this . pointerDownTime > 250
398
+ ) {
399
+ const amtPerPixel = this . stepperpixel || this . _step ;
400
+ const dist : number =
401
+ event . clientX - this . pointerDragXLocation ;
402
+ const delta =
403
+ Math . round ( dist * amtPerPixel ) *
404
+ ( event . shiftKey ? this . stepModifier : 1 ) ;
405
+ this . scrubDistance += Math . abs ( dist ) ;
406
+ this . pointerDragXLocation = event . clientX ;
407
+ this . stepBy ( delta ) ;
408
+ event . preventDefault ( ) ;
409
+ }
410
+ break ;
411
+
412
+ default :
413
+ this . pointerDragXLocation = undefined ;
414
+ this . scrubbing = false ;
415
+ this . inputElement . disabled = false ;
416
+ this . removeEventListener ( 'pointermove' , this . handlePointermove ) ;
417
+ this . removeEventListener ( 'pointerup' , this . handlePointerup ) ;
418
+ this . removeEventListener ( 'pointercancel' , this . handlePointerup ) ;
419
+
420
+ // if user has scrubbed, disallow focus of field
421
+ const bounds = this . getBoundingClientRect ( ) ;
422
+ if (
423
+ this . scrubDistance > 0 &&
424
+ this . pointerDownTime &&
425
+ Date . now ( ) - this . pointerDownTime > 250
426
+ ) {
427
+ event . preventDefault ( ) ;
428
+ } else if (
429
+ event . clientX >= bounds . x &&
430
+ event . clientX <= bounds . x + bounds . width &&
431
+ event . clientY >= bounds . y &&
432
+ event . clientY <= bounds . y + bounds . height
433
+ ) {
434
+ this . focus ( ) ;
435
+ }
436
+ this . scrubDistance = 0 ;
437
+ this . pointerDownTime = undefined ;
438
+ break ;
439
+ }
440
+ }
441
+
343
442
private handleKeydown ( event : KeyboardEvent ) : void {
344
443
if ( this . isComposing ) return ;
444
+ this . shiftPressed = event . shiftKey ;
345
445
switch ( event . code ) {
346
446
case 'ArrowUp' :
347
447
event . preventDefault ( ) ;
@@ -356,6 +456,10 @@ export class NumberField extends TextfieldBase {
356
456
}
357
457
}
358
458
459
+ private handleKeyup ( event : KeyboardEvent ) : void {
460
+ this . shiftPressed = event . shiftKey ;
461
+ }
462
+
359
463
private queuedChangeEvent ! : number ;
360
464
361
465
protected onScroll ( event : WheelEvent ) : void {
@@ -375,6 +479,9 @@ export class NumberField extends TextfieldBase {
375
479
}
376
480
377
481
protected override onFocus ( ) : void {
482
+ if ( this . pointerDragXLocation ) {
483
+ return ;
484
+ }
378
485
super . onFocus ( ) ;
379
486
this . _trackingValue = this . inputValue ;
380
487
this . keyboardFocused = ! this . readonly && true ;
@@ -639,7 +746,10 @@ export class NumberField extends TextfieldBase {
639
746
@focusin =${ this . handleFocusin }
640
747
@focusout =${ this . handleFocusout }
641
748
${ streamingListener ( {
642
- start : [ 'pointerdown' , this . handlePointerdown ] ,
749
+ start : [
750
+ 'pointerdown' ,
751
+ this . handleButtonPointerdown ,
752
+ ] ,
643
753
streamInside : [
644
754
[
645
755
'pointermove' ,
@@ -648,15 +758,15 @@ export class NumberField extends TextfieldBase {
648
758
'pointerover' ,
649
759
'pointerout' ,
650
760
] ,
651
- this . handlePointermove ,
761
+ this . handleButtonPointermove ,
652
762
] ,
653
763
end : [
654
764
[
655
765
'pointerup' ,
656
766
'pointercancel' ,
657
767
'pointerleave' ,
658
768
] ,
659
- this . handlePointerup ,
769
+ this . handleButtonPointerup ,
660
770
] ,
661
771
} ) }
662
772
>
@@ -729,6 +839,8 @@ export class NumberField extends TextfieldBase {
729
839
this . addEventListener ( 'keydown' , this . handleKeydown ) ;
730
840
this . addEventListener ( 'compositionstart' , this . handleCompositionStart ) ;
731
841
this . addEventListener ( 'compositionend' , this . handleCompositionEnd ) ;
842
+ this . addEventListener ( 'keydown' , this . handleKeyup ) ;
843
+ this . addEventListener ( 'pointerdown' , this . handlePointerdown ) ;
732
844
}
733
845
734
846
protected override updated ( changes : PropertyValues < this> ) : void {
0 commit comments