license import { ElementRef } from '@angular/core' ; import { DragDropRegistry } from './drag-drop-registry' ; import { Directionality } from '@angular/cdk/bidi' ; import { Subject } from 'rxjs' ; import { moveItemInArray } from './drag-utils' ; import { DragRefInternal as DragRef } from './drag-ref' ; /** Counter used to generate unique ids for drop refs. */ let _uniqueIdCounter = 0 ; /**
 * Proximity, as a ratio to width/height, at which a
 * dragged item will affect the drop container.
 */ const DROP_PROXIMITY_THRESHOLD = 0.05 ; docs-private interface PositionCache { /** Cached positions of the items in the list. */ items : ItemPositionCacheEntry [ ] ; /** Cached positions of the connected lists. */ siblings : ListPositionCacheEntry [ ] ; /** Dimensions of the list itself. */ self : ClientRect ; } docs-private interface ItemPositionCacheEntry { /** Instance of the drag item. */ drag : DragRef ; /** Dimensions of the item. */ clientRect : ClientRect ; /** Amount by which the item has been moved since dragging started. */ offset : number ; } docs-private interface ListPositionCacheEntry { /** Instance of the drop list. */ drop : DropListRef ; /** Dimensions of the list. */ clientRect : ClientRect ; } docs-private export interface DropListRefInternal extends DropListRef { } docs-private export class DropListRef < T = any > { private _document : Document ; deprecated breaking-change id = `cdk-drop-list-ref-${ _uniqueIdCounter ++ }` ; /** Whether starting a dragging sequence from this container is disabled. */ disabled : boolean = false ; /** Locks the position of the draggable elements inside the container along the specified axis. */ lockAxis : 'x' | 'y' ; /**
     * Function that is used to determine whether an item
     * is allowed to be moved into a drop container.
     */ enterPredicate : ( drag : DragRef , drop : DropListRef ) => boolean = ( ) => true ; /** Emits right before dragging has started. */ beforeStarted = new Subject < void > ( ) ; /**
     * Emits when the user has moved a new drag item into this container.
     */ entered = new Subject < { item : DragRef ; container : DropListRef ; } > ( ) ; /**
     * Emits when the user removes an item from the container
     * by dragging it into another container.
     */ exited = new Subject < { item : DragRef ; container : DropListRef ; } > ( ) ; /** Emits when the user drops an item inside the container. */ dropped = new Subject < { item : DragRef ; currentIndex : number ; previousIndex : number ; container : DropListRef ; previousContainer : DropListRef ; isPointerOverContainer : boolean ; } > ( ) ; /** Emits as the user is swapping items while actively dragging. */ sorted = new Subject < { previousIndex : number ; currentIndex : number ; container : DropListRef ; item : DragRef ; } > ( ) ; /** Arbitrary data that can be attached to the drop list. */ data : T ; /** Whether an item in the list is being dragged. */ private _isDragging = false ; /** Cache of the dimensions of all the items and the sibling containers. */ private _positionCache : PositionCache = { items : [ ] , siblings : [ ] , self : { } as ClientRect } ; /**
     * Draggable items that are currently active inside the container. Includes the items
     * from `_draggables`, as well as any items that have been dragged in, but haven't
     * been dropped yet.
     */ private _activeDraggables : DragRef [ ] ; /**
     * Keeps track of the item that was last swapped with the dragged item, as
     * well as what direction the pointer was moving in when the swap occured.
     */ private _previousSwap = { drag : null as DragRef | null , delta : 0 } ; /**
     * Draggable items in the container.
     * TODO(crisbeto): support arrays.
     */ private _draggables : DragRef [ ] ; private _siblings : DropListRef [ ] = [ ] ; /** Direction in which the list is oriented. */ private _orientation : 'horizontal' | 'vertical' = 'vertical' ; /** Amount of connected siblings that currently have a dragged item. */ private _activeSiblings = 0 ; constructor ( public element , private _dragDropRegistry , _document , private _dir ? ) { _dragDropRegistry . registerDropContainer ( this ) ; this . _document = _document ; } /** Removes the drop list functionality from the DOM element. */ dispose ( ) { this . beforeStarted . complete ( ) ; this . entered . complete ( ) ; this . exited . complete ( ) ; this . dropped . complete ( ) ; this . sorted . complete ( ) ; this . _dragDropRegistry . removeDropContainer ( this ) ; } /** Whether an item from this list is currently being dragged. */ isDragging ( ) { return this . _isDragging ; } /** Starts dragging an item. */ start ( ) { this . beforeStarted . next ( ) ; this . _isDragging = true ; this . _activeDraggables = this . _draggables . slice ( ) ; this . _cachePositions ( ) ; this . _positionCache . siblings . forEach ( sibling => sibling . drop . _toggleIsReceiving ( true ) ) ; } param item param pointerX param pointerY enter ( item , pointerX , pointerY ) { this . entered . next ( { item , container : this } ) ; this . start ( ) ; const newIndex = this . _getItemIndexFromPointerPosition ( item , pointerX , pointerY ) ; const currentIndex = this . _activeDraggables . indexOf ( item ) ; const newPositionReference = this . _activeDraggables [ newIndex ] ; const placeholder = item . getPlaceholderElement ( ) ; if ( currentIndex > - 1 ) { this . _activeDraggables . splice ( currentIndex , 1 ) ; } if ( newPositionReference && ! this . _dragDropRegistry . isDragging ( newPositionReference ) ) { const element = newPositionReference . getRootElement ( ) ; element . parentElement ! . insertBefore ( placeholder , element ) ; this . _activeDraggables . splice ( newIndex , 0 , item ) ; } else { this . element . nativeElement . appendChild ( placeholder ) ; this . _activeDraggables . push ( item ) ; } placeholder . style . transform = '' ; this . _cachePositions ( ) ; } param item exit ( item ) { this . _reset ( ) ; this . exited . next ( { item , container : this } ) ; } param item param currentIndex param previousContainer param isPointerOverContainer drop ( item , currentIndex , previousContainer , isPointerOverContainer ) { this . _reset ( ) ; this . dropped . next ( { item , currentIndex , previousIndex : previousContainer . getItemIndex ( item ) , container : this , previousContainer , isPointerOverContainer } ) ; } param items withItems ( items ) { this . _draggables = items . slice ( ) ; return this ; } param connectedTo connectedTo ( connectedTo ) { this . _siblings = connectedTo . slice ( ) ; return this ; } param orientation withOrientation ( orientation ) { this . _orientation = orientation ; return this ; } param item getItemIndex ( item ) { if ( ! this . _isDragging ) { return this . _draggables . indexOf ( item ) ; } const items = this . _orientation === 'horizontal' && this . _dir && this . _dir . value === 'rtl' ? this . _positionCache . items . slice ( ) . reverse ( ) : this . _positionCache . items ; return findIndex ( items , currentItem => currentItem . drag === item ) ; } /**
     * Whether the list is able to receive the item that
     * is currently being dragged inside a connected drop list.
     */ isReceiving ( ) { return this . _activeSiblings > 0 ; } param item param pointerX param pointerY param pointerDelta _sortItem ( item , pointerX , pointerY , pointerDelta ) { if ( ! this . _isPointerNearDropContainer ( pointerX , pointerY ) ) { return ; } const siblings = this . _positionCache . items ; const newIndex = this . _getItemIndexFromPointerPosition ( item , pointerX , pointerY , pointerDelta ) ; if ( newIndex === - 1 && siblings . length > 0 ) { return ; } const isHorizontal = this . _orientation === 'horizontal' ; const currentIndex = findIndex ( siblings , currentItem => currentItem . drag === item ) ; const siblingAtNewPosition = siblings [ newIndex ] ; const currentPosition = siblings [ currentIndex ] . clientRect ; const newPosition = siblingAtNewPosition . clientRect ; const delta = currentIndex > newIndex ? 1 : - 1 ; this . _previousSwap . drag = siblingAtNewPosition . drag ; this . _previousSwap . delta = isHorizontal ? pointerDelta . x : pointerDelta . y ; const itemOffset = this . _getItemOffsetPx ( currentPosition , newPosition , delta ) ; const siblingOffset = this . _getSiblingOffsetPx ( currentIndex , siblings , delta ) ; const oldOrder = siblings . slice ( ) ; moveItemInArray ( siblings , currentIndex , newIndex ) ; this . sorted . next ( { previousIndex : currentIndex , currentIndex : newIndex , container : this , item } ) ; siblings . forEach ( ( sibling , index ) => { if ( oldOrder [ index ] === sibling ) { return ; } const isDraggedItem = sibling . drag === item ; const offset = isDraggedItem ? itemOffset : siblingOffset ; const elementToOffset = isDraggedItem ? item . getPlaceholderElement ( ) : sibling . drag . getRootElement ( ) ; sibling . offset += offset ; if ( isHorizontal ) { elementToOffset . style . transform = `translate3d(${ Math . round ( sibling . offset ) }px, 0, 0)` ; adjustClientRect ( sibling . clientRect , 0 , offset ) ; } else { elementToOffset . style . transform = `translate3d(0, ${ Math . round ( sibling . offset ) }px, 0)` ; adjustClientRect ( sibling . clientRect , offset , 0 ) ; } } ) ; } /** Refreshes the position cache of the items and sibling containers. */ private _cachePositions ( ) { const isHorizontal = this . _orientation === 'horizontal' ; this . _positionCache . self = this . element . nativeElement . getBoundingClientRect ( ) ; this . _positionCache . items = this . _activeDraggables . map ( drag => { const elementToMeasure = this . _dragDropRegistry . isDragging ( drag ) ? drag . getPlaceholderElement ( ) : drag . getRootElement ( ) ; const clientRect = elementToMeasure . getBoundingClientRect ( ) ; return { drag , offset : 0 , clientRect : { top : clientRect . top , right : clientRect . right , bottom : clientRect . bottom , left : clientRect . left , width : clientRect . width , height : clientRect . height } } ; } ) . sort ( ( a , b ) => { return isHorizontal ? a . clientRect . left - b . clientRect . left : a . clientRect . top - b . clientRect . top ; } ) ; this . _positionCache . siblings = this . _siblings . map ( drop => ( { drop , clientRect : drop . element . nativeElement . getBoundingClientRect ( ) } ) ) ; } /**
     * Toggles whether the list can receive the item that is currently being dragged.
     * Usually called by a sibling that initiated the dragging.
     */ _toggleIsReceiving ( isDragging ) { this . _activeSiblings = Math . max ( 0 , this . _activeSiblings + ( isDragging ? 1 : - 1 ) ) ; } /** Resets the container to its initial state. */ private _reset ( ) { this . _isDragging = false ; this . _activeDraggables . forEach ( item => item . getRootElement ( ) . style . transform = '' ) ; this . _positionCache . siblings . forEach ( sibling => sibling . drop . _toggleIsReceiving ( false ) ) ; this . _activeDraggables = [ ] ; this . _positionCache . items = [ ] ; this . _positionCache . siblings = [ ] ; this . _previousSwap . drag = null ; this . _previousSwap . delta = 0 ; } param currentIndex param siblings param delta private _getSiblingOffsetPx ( currentIndex , siblings , delta ) { const isHorizontal = this . _orientation === 'horizontal' ; const currentPosition = siblings [ currentIndex ] . clientRect ; const immediateSibling = siblings [ currentIndex + delta * - 1 ] ; let siblingOffset = currentPosition [ isHorizontal ? 'width' : 'height' ] * delta ; if ( immediateSibling ) { const start = isHorizontal ? 'left' : 'top' ; const end = isHorizontal ? 'right' : 'bottom' ; if ( delta === - 1 ) { siblingOffset -= immediateSibling . clientRect [ start ] - currentPosition [ end ] ; } else { siblingOffset += currentPosition [ start ] - immediateSibling . clientRect [ end ] ; } } return siblingOffset ; } param pointerX param pointerY private _isPointerNearDropContainer ( pointerX , pointerY ) { const { top , right , bottom , left , width , height } = this . _positionCache . self ; const xThreshold = width * DROP_PROXIMITY_THRESHOLD ; const yThreshold = height * DROP_PROXIMITY_THRESHOLD ; return pointerY > top - yThreshold && pointerY < bottom + yThreshold && pointerX > left - xThreshold && pointerX < right + xThreshold ; } param currentPosition param newPosition param delta private _getItemOffsetPx ( currentPosition , newPosition , delta ) { const isHorizontal = this . _orientation === 'horizontal' ; let itemOffset = isHorizontal ? newPosition . left - currentPosition . left : newPosition . top - currentPosition . top ; if ( delta === - 1 ) { itemOffset += isHorizontal ? newPosition . width - currentPosition . width : newPosition . height - currentPosition . height ; } return itemOffset ; } param item param pointerX param pointerY param delta private _getItemIndexFromPointerPosition ( item , pointerX , pointerY , delta ? ) { const isHorizontal = this . _orientation === 'horizontal' ; return findIndex ( this . _positionCache . items , ( { drag , clientRect } , _ , array ) => { if ( drag === item ) { return array . length < 2 ; } if ( delta ) { const direction = isHorizontal ? delta . x : delta . y ; if ( drag === this . _previousSwap . drag && direction === this . _previousSwap . delta ) { return false ; } } return isHorizontal ? pointerX >= Math . floor ( clientRect . left ) && pointerX <= Math . floor ( clientRect . right ) : pointerY >= Math . floor ( clientRect . top ) && pointerY <= Math . floor ( clientRect . bottom ) ; } ) ; } param x param y _isOverContainer ( x , y ) { return isInsideClientRect ( this . _positionCache . self , x , y ) ; } param item param x param y _getSiblingContainerFromPosition ( item , x , y ) { const results = this . _positionCache . siblings . filter ( sibling => { return isInsideClientRect ( sibling . clientRect , x , y ) ; } ) ; if ( ! results . length ) { return null ; } const elementFromPoint = this . _document . elementFromPoint ( x , y ) ; if ( ! elementFromPoint ) { return null ; } const result = results . find ( sibling => { const element = sibling . drop . element . nativeElement ; return element === elementFromPoint || element . contains ( elementFromPoint ) ; } ) ; return result && result . drop . enterPredicate ( item , result . drop ) ? result . drop : null ; } } param clientRect param top param left function adjustClientRect ( clientRect , top , left ) { clientRect . top += top ; clientRect . bottom = clientRect . top + clientRect . height ; clientRect . left += left ; clientRect . right = clientRect . left + clientRect . width ; } param array param predicate function findIndex < T > ( array , predicate ) { for ( let i = 0 ; i < array . length ; i ++ ) { if ( predicate ( array [ i ] , i , array ) ) { return i ; } } return - 1 ; } param clientRect param x param y function isInsideClientRect ( clientRect , x , y ) { const { top , bottom , left , right } = clientRect ; return y >= top && y <= bottom && x >= left && x <= right ; }