import { Point } from '../../Point' ; import { Logger } from 'polar-shared/src/logger/Logger' ; import { Selections } from '../../highlights/text/selection/Selections' ; import { Ranges } from '../../highlights/text/selection/Ranges' ; import { MouseDirection } from "./MouseDirection" ; const log = Logger . create ( ) ; type EventRemover = ( ) => void ; /**
 * Listens for when a new text selection has been created
 */ export class ActiveSelections { public static addEventListener ( listener , target = document . body ) { let originPoint ; let activeSelection ; type EventFired = 'none' | 'created' | 'destroyed' ; let eventFired = 'none' ; const handleDestroyedSelection = ( ) => { listener ( { ... activeSelection ! , type : 'destroyed' } ) ; activeSelection = undefined ; eventFired = 'destroyed' ; } ; const onMouseUp = ( event , element ) => { const handleMouseEvent = ( ) => { let hasActiveTextSelection = false ; eventFired = 'none' ; try { const view = event . view || window ; const selection = view . getSelection ( ) ! ; hasActiveTextSelection = this . hasActiveTextSelection ( selection ) ; const point = this . eventToPoint ( event ) ; if ( ! element ) { log . warn ( "No target element: " , event . target ) ; return ; } if ( hasActiveTextSelection ) { const mouseDirection = point . y - originPoint ! . y < 0 ? 'up' : 'down' ; const range = selection . getRangeAt ( 0 ) ; const boundingClientRect = range . getBoundingClientRect ( ) ; activeSelection = { element , originPoint : originPoint ! , mouseDirection , boundingClientRect , selection , view , type : 'created' } ; listener ( activeSelection ) ; eventFired = 'created' ; } else { handleDestroyedSelection ( ) ; } } finally { } } ; this . withTimeout ( ( ) => handleMouseEvent ( ) ) ; } ; type EventType = 'mouse' | 'touch' ; const onMouseDown = ( event , type ) : EventRemover => { if ( event . ctrlKey || event . metaKey ) { return ( ) => { } ; } if ( ! activeSelection ) { originPoint = this . eventToPoint ( event ) ; } const element = this . targetElementForEvent ( event ) ; const view = event . view || window ; switch ( type ) { case "mouse" : const handleMouseUp = ( event ) => { onMouseUp ( event , element ) ; } ; view . addEventListener ( 'mouseup' , handleMouseUp , { once : true } ) ; return ( ) => view . removeEventListener ( 'mouseup' , handleMouseUp ) ; case "touch" : const handleTouchEnd = ( event ) => { onMouseUp ( event , element ) ; } ; view . addEventListener ( 'touchend' , handleTouchEnd , { once : true } ) ; return ( ) => view . removeEventListener ( 'touchend' , handleTouchEnd ) ; } } ; const onKeyPress = ( event ) => { if ( event . key === 'Escape' ) { handleDestroyedSelection ( ) ; } } ; const handleMouseDown = ( event ) => onMouseDown ( event , 'mouse' ) ; const handleTouchStart = ( event ) => onMouseDown ( event , 'touch' ) ; target . addEventListener ( 'mousedown' , handleMouseDown ) ; target . addEventListener ( 'touchstart' , handleTouchStart ) ; return ( ) => { target . removeEventListener ( 'mousedown' , handleMouseDown ) ; target . removeEventListener ( 'touchstart' , handleTouchStart ) ; } ; } private static withTimeout ( callback ) { setTimeout ( ( ) => callback ( ) , 1 ) ; } private static targetElementForEvent ( event ) { const anyTarget = event . target as any ; if ( anyTarget . nodeType === Node . ELEMENT_NODE ) { return event . target as HTMLElement ; } else { return ( event . target ! as any ) . parentElement ! as HTMLElement ; } } private static hasActiveTextSelection ( selection ) { const ranges = Selections . toRanges ( selection ) ; for ( const range of ranges ) { if ( Ranges . hasText ( range ) ) { return true ; } } return false ; } private static eventToPoint ( event ) { if ( 'offsetX' in event ) { return { x : event . offsetX , y : event . offsetY } ; } if ( ! event . target ) { throw new Error ( "No target" ) ; } if ( event . changedTouches . length === 0 ) { log . warn ( "No touches found in event: " , event ) ; throw new Error ( "No touches" ) ; } const rect = ( < HTMLElement > event . target ) . getBoundingClientRect ( ) ; const x = event . changedTouches [ 0 ] . pageX - rect . left ; const y = event . changedTouches [ 0 ] . pageY - rect . top ; return { x , y } ; } } export interface ActiveSelectionListener { ( event ) : void ; } export interface ActiveSelection { readonly element : HTMLElement ; readonly originPoint : Point ; readonly mouseDirection : MouseDirection ; readonly boundingClientRect : ClientRect | DOMRect ; /**
     * The actual selection object that we're working with.
     */ readonly selection : Selection ; readonly view : Window ; readonly type : ActiveSelectionType ; } export type ActiveSelectionType = 'created' | 'destroyed' ; export interface ActiveSelectionEvent extends ActiveSelection { }