import { OrderedSet , AEventDispatcher , IEventListener } from '../internal' ; import { IGroupData , IGroupItem , isGroup } from '../model' ; import type { IDataProvider } from '../provider' ; import { cssClass , engineCssClass } from '../styles' ; import { forEachIndices } from '../model/internal' ; import { rangeSelection } from '../provider/utils' ; interface IPoint { x : number ; y : number ; } interface IShift { xShift : number ; yShift : number ; node : HTMLElement ; } asMemberOf internal event export declare function selectRange ( from , to , additional ) ; internal export default class SelectionManager extends AEventDispatcher { static readonly EVENT_SELECT_RANGE = 'selectRange' ; private static readonly MIN_DISTANCE = 10 ; private readonly hr : HTMLHRElement ; private start : ( IPoint & IShift ) | null = null ; constructor ( private readonly ctx , private readonly body ) { super ( ) ; const root = body . parentElement ! . parentElement ! ; let hr = root . querySelector ( 'hr' ) ; if ( ! hr ) { hr = root . ownerDocument ! . createElement ( 'hr' ) ; root . appendChild ( hr ) ; } this . hr = hr ; this . hr . classList . add ( cssClass ( 'hr' ) ) ; const mouseMove = ( evt ) => { this . showHint ( evt ) ; } ; const mouseUp = ( evt ) => { this . body . removeEventListener ( 'mousemove' , mouseMove ) ; this . body . removeEventListener ( 'mouseup' , mouseUp ) ; this . body . removeEventListener ( 'mouseleave' , mouseUp ) ; if ( ! this . start ) { return ; } const row = engineCssClass ( 'tr' ) ; const startNode = this . start . node . classList . contains ( row ) ? this . start . node : this . start . node . closest < HTMLElement > ( `.${ row }` ) ; const end = this . body . ownerDocument ! . elementFromPoint ( evt . clientX , evt . clientY ) as HTMLElement ; const endNode = end . classList . contains ( row ) ? end : end . closest < HTMLElement > ( `.${ row }` ) ; this . start = null ; this . body . classList . remove ( cssClass ( 'selection-active' ) ) ; this . hr . classList . remove ( cssClass ( 'selection-active' ) ) ; this . select ( evt . ctrlKey , startNode , endNode ) ; } ; body . addEventListener ( 'mousedown' , ( evt ) => { const r = root . getBoundingClientRect ( ) ; this . start = { x : evt . clientX , y : evt . clientY , xShift : r . left , yShift : r . top , node : evt . target as HTMLElement } ; this . body . classList . add ( cssClass ( 'selection-active' ) ) ; body . addEventListener ( 'mousemove' , mouseMove , { passive : true , } ) ; body . addEventListener ( 'mouseup' , mouseUp , { passive : true , } ) ; body . addEventListener ( 'mouseleave' , mouseUp , { passive : true , } ) ; } , { passive : true , } ) ; } protected createEventList ( ) { return super . createEventList ( ) . concat ( [ SelectionManager . EVENT_SELECT_RANGE ] ) ; } on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) { return super . on ( type , listener ) ; } private select ( additional , startNode ? , endNode ? ) { if ( ! startNode || ! endNode || startNode === endNode ) { return ; } const startIndex = Number . parseInt ( startNode . dataset . index ! , 10 ) ; const endIndex = Number . parseInt ( endNode . dataset . index ! , 10 ) ; const from = Math . min ( startIndex , endIndex ) ; const end = Math . max ( startIndex , endIndex ) ; if ( from === end ) { return ; } requestAnimationFrame ( ( ) => this . fire ( SelectionManager . EVENT_SELECT_RANGE , from , end , additional ) ) ; } private showHint ( end ) { const start = this . start ! ; const sy = start . y ; const ey = end . clientY ; const visible = Math . abs ( sy - ey ) > SelectionManager . MIN_DISTANCE ; this . hr . classList . toggle ( cssClass ( 'selection-active' ) , visible ) ; this . hr . style . transform = `translate(${ start . x - start . xShift }px,${ sy - start . yShift }px)scale(1,${ Math . abs ( ey - sy ) })rotate(${ ey > sy ? 90 : - 90 }deg)` ; } remove ( node ) { node . onclick = undefined ; } add ( node ) { node . onclick = ( evt ) => { const dataIndex = Number . parseInt ( node . dataset . i ! , 10 ) ; if ( evt . shiftKey ) { const relIndex = Number . parseInt ( node . dataset . index ! , 10 ) ; const ranking = node . parentElement ! . dataset . ranking ! ; if ( rangeSelection ( this . ctx . provider , ranking , dataIndex , relIndex , evt . ctrlKey ) ) { return ; } } this . ctx . provider . toggleSelection ( dataIndex , evt . ctrlKey ) ; } ; } selectRange ( rows , additional = false ) { const current = new OrderedSet < number > ( additional ? this . ctx . provider . getSelection ( ) : [ ] ) ; const toggle = ( dataIndex ) => { if ( current . has ( dataIndex ) ) { current . delete ( dataIndex ) ; } else { current . add ( dataIndex ) ; } } ; rows . forEach ( ( d ) => { if ( isGroup ( d ) ) { forEachIndices ( d . order , toggle ) ; } else { toggle ( d . dataIndex ) ; } } ) ; this . ctx . provider . setSelection ( Array . from ( current ) ) ; } updateState ( node , dataIndex ) { if ( this . ctx . provider . isSelected ( dataIndex ) ) { node . classList . add ( cssClass ( 'selected' ) ) ; } else { node . classList . remove ( cssClass ( 'selected' ) ) ; } } update ( node , selectedDataIndices ) { const dataIndex = Number . parseInt ( node . dataset . i ! , 10 ) ; if ( selectedDataIndices . has ( dataIndex ) ) { node . classList . add ( cssClass ( 'selected' ) ) ; } else { node . classList . remove ( cssClass ( 'selected' ) ) ; } } }