@@ -62,6 +62,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
6262 xOffset : 0 ,
6363 yOffset : 0
6464 } ;
65+ /** @internal element we bound the touch handlers to in _mouseDown(), so _mouseUp() can un-bind the same one */
66+ protected _touchTarget ?: HTMLElement ;
6567 /** @internal auto-scroll animation variables */
6668 protected _autoScrollAnimId ?: number ;
6769 protected _autoScrollContainer ?: HTMLElement ;
@@ -222,9 +224,13 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
222224 document . addEventListener ( 'mousemove' , this . _mouseMove , { capture : true , passive : true } ) ; // true=capture, not bubble
223225 document . addEventListener ( 'mouseup' , this . _mouseUp as EventListener , true ) ;
224226 if ( isTouch && e . currentTarget ) {
225- ( e . currentTarget as HTMLElement ) . addEventListener ( 'touchmove' , touchmove ) ;
226- ( e . currentTarget as HTMLElement ) . addEventListener ( 'touchend' , touchend ) ;
227- ( e . currentTarget as HTMLElement ) . addEventListener ( 'touchcancel' , touchend ) ; // browser aborting on us, clean up too
227+ // remember what we bound to: _mouseUp() is a document handler so its own currentTarget is
228+ // NOT this element, and these were added bubbling - removing with capture=true never matched,
229+ // leaking a handler per drag that then replayed each touchmove N times over (#2666).
230+ this . _touchTarget = e . currentTarget as HTMLElement ;
231+ this . _touchTarget . addEventListener ( 'touchmove' , touchmove ) ;
232+ this . _touchTarget . addEventListener ( 'touchend' , touchend ) ;
233+ this . _touchTarget . addEventListener ( 'touchcancel' , touchend ) ; // browser aborting on us, clean up too
228234 }
229235 if ( DDTouch . wasDelayed ) {
230236 // the long-press wait just elapsed - signal the item is now armed & ready to drag
@@ -306,10 +312,11 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
306312 this . el . classList . remove ( 'ui-draggable-armed' ) ;
307313 document . removeEventListener ( 'mousemove' , this . _mouseMove , true ) ;
308314 document . removeEventListener ( 'mouseup' , this . _mouseUp as EventListener , true ) ;
309- if ( isTouch && e . currentTarget ) { // destroy() during nested grid call us again wit fake _mouseUp
310- e . currentTarget . removeEventListener ( 'touchmove' , touchmove as EventListener , true ) ;
311- e . currentTarget . removeEventListener ( 'touchend' , touchend as EventListener , true ) ;
312- e . currentTarget . removeEventListener ( 'touchcancel' , touchend as EventListener , true ) ;
315+ if ( isTouch && this . _touchTarget ) { // destroy() during nested grid call us again wit fake _mouseUp
316+ this . _touchTarget . removeEventListener ( 'touchmove' , touchmove as EventListener ) ;
317+ this . _touchTarget . removeEventListener ( 'touchend' , touchend as EventListener ) ;
318+ this . _touchTarget . removeEventListener ( 'touchcancel' , touchend as EventListener ) ;
319+ delete this . _touchTarget ;
313320 }
314321 if ( this . dragging ) {
315322 delete this . dragging ;
@@ -512,10 +519,11 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
512519 protected _getClipping ( el : HTMLElement , scrollEl : HTMLElement ) : number {
513520 const elRect = el . getBoundingClientRect ( ) ;
514521 const scrollRect = scrollEl . getBoundingClientRect ( ) ;
515- const viewportH = window . innerHeight || document . documentElement . clientHeight ;
522+ // what's really on screen, NOT window.innerHeight which sits under iOS's overlaying tool bars (#2666)
523+ const view = Utils . getVisibleViewport ( ) ;
516524 if ( elRect . bottom < scrollRect . top || elRect . top > scrollRect . bottom ) return 0 ; // fully outside
517- const clippedBelow = elRect . bottom - Math . min ( scrollRect . bottom , viewportH ) ;
518- const clippedAbove = elRect . top - Math . max ( scrollRect . top , 0 ) ;
525+ const clippedBelow = elRect . bottom - Math . min ( scrollRect . bottom , view . bottom ) ;
526+ const clippedAbove = elRect . top - Math . max ( scrollRect . top , view . top ) ;
519527 if ( clippedAbove < 0 ) return clippedAbove ;
520528 if ( clippedBelow > 0 ) return clippedBelow ;
521529 return 0 ;
@@ -530,8 +538,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
530538 if ( clipping === 0 ) { this . _stopScrolling ( ) ; return ; }
531539
532540 if ( ! this . _autoScrollMaxSpeed ) {
533- const viewportH = window . innerHeight || document . documentElement . clientHeight ;
534- this . _autoScrollMaxSpeed = Math . max ( viewportH / 150 , 4 ) ;
541+ const view = Utils . getVisibleViewport ( ) ;
542+ this . _autoScrollMaxSpeed = Math . max ( ( view . bottom - view . top ) / 150 , 4 ) ;
535543 }
536544 const absPx = Math . abs ( clipping ) ;
537545 const speed = Math . min ( absPx * 0.5 , this . _autoScrollMaxSpeed ) ;
0 commit comments