import { normV , subV2 } from '@models/utils' ; import React , { useEffect , useRef } from 'react' ; function checkClass < ET extends Element > ( el , stop , clsToFind ) { let cur = el ; while ( cur && cur !== stop ) { if ( cur . attributes ) { const cls = cur . attributes . getNamedItem ( 'class' ) ; if ( cls ) { if ( cls . value . includes ( clsToFind ) ) { return cur ; } } } cur = cur . parentNode as Element ; } return null ; } export interface DragState < ET extends Element > { buttons : number ; xy : [ number , number ] ; start : [ number , number ] ; startTime : number ; dragging : boolean ; event : React . PointerEvent < ET > | undefined ; } interface DragMemo < ET extends Element > { timerAgain : boolean ; timerId : number ; state : DragState < ET > ; } export function DragHandler < ET extends Element > ( onDrag , handle ? , onTimer ? , interval ? , onContextMenu ? ) { const target = useRef < ET > ( null ) ; const memo = useRef < DragMemo < ET > > ( { state : { } } as DragMemo < ET > ) . current ; const timerFunc = ( ) => { if ( ! memo . state . dragging && ! memo . timerAgain ) { clearInterval ( memo . timerId ) ; memo . timerId = 0 ; } else if ( onTimer ) { memo . timerAgain = onTimer ( memo . state ) ; } } ; function onTouchMove ( ev ) { ev . preventDefault ( ) ; ev . stopPropagation ( ) ; } useEffect ( ( ) => { const current = target . current ; current ?. addEventListener ( 'touchmove' , onTouchMove , { passive : false } ) ; return ( ) => { current ?. removeEventListener ( 'touchmove' , onTouchMove ) ; } ; } , [ target . current ] ) ; const bindObject = { target : target , onMouseDown : ( e ) => { e . stopPropagation ( ) ; } , onTouchStart : ( e ) => { e . stopPropagation ( ) ; } , onPointerDown : ( e ) => { e . stopPropagation ( ) ; memo . state = { dragging : false , buttons : e . buttons , xy : [ e . clientX , e . clientY ] , start : [ e . clientX , e . clientY ] , startTime : Date . now ( ) , event : e } ; if ( ( e . buttons & 1 ) && target . current && ( ! handle || checkClass ( e . target as Element , target . current , handle ) ) ) { ( e . target as Element ) . setPointerCapture ( e . pointerId ) ; if ( ! memo . timerId ) { memo . timerId = setInterval ( timerFunc , interval ) ; } memo . state . dragging = true ; } } , onPointerOut : ( e ) => { e . stopPropagation ( ) ; Object . assign ( memo . state , { dragging : false , buttons : e . buttons , xy : [ e . clientX , e . clientY ] , event : e } ) ; } , onPointerUp : ( e ) => { bindObject . onPointerOut ( e ) ; } , onTouchEnd : ( e ) => { e . stopPropagation ( ) ; const delta = normV ( subV2 ( memo . state . xy , memo . state . start ) ) ; const deltaT = Date . now ( ) - memo . state . startTime ; if ( delta < 5 && deltaT > 0.5 && onContextMenu ) { onContextMenu ( e ) ; } } , onPointerMove : ( e ) => { e . stopPropagation ( ) ; Object . assign ( memo . state , { dragging : memo . state ?. dragging ? true : false , buttons : e . buttons , xy : [ e . clientX , e . clientY ] , event : e } ) ; if ( memo . state . dragging ) { if ( ( e . buttons & 1 ) ) { onDrag ( memo . state ) ; } else { memo . state . dragging = false ; } } } , } ; return bindObject ; }