license import { EmbeddedViewRef , ElementRef , NgZone , ViewContainerRef , TemplateRef } from '@angular/core' ; import { ViewportRuler } from '@angular/cdk/scrolling' ; import { Directionality } from '@angular/cdk/bidi' ; import { normalizePassiveListenerOptions } from '@angular/cdk/platform' ; import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { Subscription , Subject , Observable , Observer } from 'rxjs' ; import { DropListRefInternal as DropListRef } from './drop-list-ref' ; import { DragDropRegistry } from './drag-drop-registry' ; import { extendStyles , toggleNativeDragInteractions } from './drag-styling' ; import { getTransformTransitionDurationInMs } from './transition-duration' ; /** Object that can be used to configure the behavior of DragRef. */ export interface DragRefConfig { /**
     * Minimum amount of pixels that the user should
     * drag, before the CDK initiates a drag sequence.
     */ dragStartThreshold : number ; /**
     * Amount the pixels the user should drag before the CDK
     * considers them to have changed the drag direction.
     */ pointerDirectionChangeThreshold : number ; } /** Options that can be used to bind a passive event listener. */ const passiveEventListenerOptions = normalizePassiveListenerOptions ( { passive : true } ) ; /** Options that can be used to bind an active event listener. */ const activeEventListenerOptions = normalizePassiveListenerOptions ( { passive : false } ) ; /**
 * Time in milliseconds for which to ignore mouse events, after
 * receiving a touch event. Used to avoid doing double work for
 * touch devices where the browser fires fake mouse events, in
 * addition to touch events.
 */ const MOUSE_EVENT_IGNORE_TIME = 800 ; /**
 * Template that can be used to create a drag helper element (e.g. a preview or a placeholder).
 */ interface DragHelperTemplate < T = any > { templateRef : TemplateRef < T > ; data : T ; } interface DragHandle { element : ElementRef < HTMLElement > ; disabled : boolean ; } docs-private export interface DragRefInternal extends DragRef { } docs-private export class DragRef < T = any > { /** Element displayed next to the user's pointer while the element is dragged. */ private _preview : HTMLElement ; /** Reference to the view of the preview element. */ private _previewRef : EmbeddedViewRef < any > | null ; /** Reference to the view of the placeholder element. */ private _placeholderRef : EmbeddedViewRef < any > | null ; /** Element that is rendered instead of the draggable item while it is being sorted. */ private _placeholder : HTMLElement ; /** Coordinates within the element at which the user picked up the element. */ private _pickupPositionInElement : Point ; /** Coordinates on the page at which the user picked up the element. */ private _pickupPositionOnPage : Point ; /**
     * Reference to the element that comes after the draggable in the DOM, at the time
     * it was picked up. Used for restoring its initial position when it's dropped.
     */ private _nextSibling : Node | null ; /**
     * CSS `transform` applied to the element when it isn't being dragged. We need a
     * passive transform in order for the dragged element to retain its new position
     * after the user has stopped dragging and because we need to know the relative
     * position in case they start dragging again. This corresponds to `element.style.transform`.
     */ private _passiveTransform : Point = { x : 0 , y : 0 } ; /** CSS `transform` that is applied to the element while it's being dragged. */ private _activeTransform : Point = { x : 0 , y : 0 } ; /** Inline `transform` value that the element had before the first dragging sequence. */ private _initialTransform ? : string ; /**
     * Whether the dragging sequence has been started. Doesn't
     * necessarily mean that the element has been moved.
     */ private _hasStartedDragging : boolean ; /** Whether the element has moved since the user started dragging it. */ private _hasMoved : boolean ; /** Drop container in which the DragRef resided when dragging began. */ private _initialContainer : DropListRef ; /** Cached scroll position on the page when the element was picked up. */ private _scrollPosition : { top : number ; left : number ; } ; /** Emits when the item is being moved. */ private _moveEvents = new Subject < { source : DragRef ; pointerPosition : { x : number ; y : number ; } ; event : MouseEvent | TouchEvent ; delta : { x : - 1 | 0 | 1 ; y : - 1 | 0 | 1 ; } ; } > ( ) ; /**
     * Amount of subscriptions to the move event. Used to avoid
     * hitting the zone if the consumer didn't subscribe to it.
     */ private _moveEventSubscriptions = 0 ; /** Keeps track of the direction in which the user is dragging along each axis. */ private _pointerDirectionDelta : { x : - 1 | 0 | 1 ; y : - 1 | 0 | 1 ; } ; /** Pointer position at which the last change in the delta occurred. */ private _pointerPositionAtLastDirectionChange : Point ; /**
     * Root DOM node of the drag instance. This is the element that will
     * be moved around as the user is dragging.
     */ private _rootElement : HTMLElement ; /**
     * Inline style value of `-webkit-tap-highlight-color` at the time the
     * dragging was started. Used to restore the value once we're done dragging.
     */ private _rootElementTapHighlight : string | null ; /** Subscription to pointer movement events. */ private _pointerMoveSubscription = Subscription . EMPTY ; /** Subscription to the event that is dispatched when the user lifts their pointer. */ private _pointerUpSubscription = Subscription . EMPTY ; /**
     * Time at which the last touch event occurred. Used to avoid firing the same
     * events multiple times on touch devices where the browser will fire a fake
     * mouse event for each touch event, after a certain time.
     */ private _lastTouchEventTime : number ; /** Cached reference to the boundary element. */ private _boundaryElement : HTMLElement | null = null ; /** Whether the native dragging interactions have been enabled on the root element. */ private _nativeInteractionsEnabled = true ; /** Cached dimensions of the preview element. */ private _previewRect ? : ClientRect ; /** Cached dimensions of the boundary element. */ private _boundaryRect ? : ClientRect ; /** Element that will be used as a template to create the draggable item's preview. */ private _previewTemplate : DragHelperTemplate | null ; /** Template for placeholder element rendered to show where a draggable would be dropped. */ private _placeholderTemplate : DragHelperTemplate | null ; /** Elements that can be used to drag the draggable item. */ private _handles : DragHandle [ ] = [ ] ; /** Axis along which dragging is locked. */ lockAxis : 'x' | 'y' ; /** Whether starting to drag this element is disabled. */ get disabled ( ) : boolean { return this . _disabled || ! ! ( this . dropContainer && this . dropContainer . disabled ) ; } set disabled ( value ) { const newValue = coerceBooleanProperty ( value ) ; if ( newValue !== this . _disabled ) { this . _disabled = newValue ; this . _toggleNativeDragInteractions ( ) ; } } private _disabled = false ; /** Emits as the drag sequence is being prepared. */ beforeStarted = new Subject < void > ( ) ; /** Emits when the user starts dragging the item. */ started = new Subject < { source : DragRef ; } > ( ) ; /** Emits when the user has released a drag item, before any animations have started. */ released = new Subject < { source : DragRef ; } > ( ) ; /** Emits when the user stops dragging an item in the container. */ ended = new Subject < { source : DragRef ; } > ( ) ; /** Emits when the user has moved the item into a new container. */ entered = new Subject < { container : DropListRef ; item : DragRef ; } > ( ) ; /** Emits when the user removes the item its container by dragging it into another container. */ exited = new Subject < { container : DropListRef ; item : DragRef ; } > ( ) ; /** Emits when the user drops the item inside a container. */ dropped = new Subject < { previousIndex : number ; currentIndex : number ; item : DragRef ; container : DropListRef ; previousContainer : DropListRef ; isPointerOverContainer : boolean ; } > ( ) ; /**
     * Emits as the user is dragging the item. Use with caution,
     * because this event will fire for every pixel that the user has dragged.
     */ moved : Observable < { source : DragRef ; pointerPosition : { x : number ; y : number ; } ; event : MouseEvent | TouchEvent ; delta : { x : - 1 | 0 | 1 ; y : - 1 | 0 | 1 ; } ; } > = Observable . create ( ( observer ) => { const subscription = this . _moveEvents . subscribe ( observer ) ; this . _moveEventSubscriptions ++ ; return ( ) => { subscription . unsubscribe ( ) ; this . _moveEventSubscriptions -- ; } ; } ) ; /** Arbitrary data that can be attached to the drag item. */ data : T ; constructor ( element , private _document , private _ngZone , private _viewContainerRef , private _viewportRuler , private _dragDropRegistry , private _config , /** Droppable container that the draggable is a part of. */ public dropContainer ? , private _dir ? ) { this . withRootElement ( element ) ; _dragDropRegistry . registerDragItem ( this ) ; } /**
     * Returns the element that is being used as a placeholder
     * while the current element is being dragged.
     */ getPlaceholderElement ( ) { return this . _placeholder ; } /** Returns the root draggable element. */ getRootElement ( ) { return this . _rootElement ; } /** Registers the handles that can be used to drag the element. */ withHandles ( handles ) { this . _handles = handles ; handles . forEach ( handle => toggleNativeDragInteractions ( handle . element . nativeElement , false ) ) ; this . _toggleNativeDragInteractions ( ) ; return this ; } /** Registers the template that should be used for the drag preview. */ withPreviewTemplate ( template ) { this . _previewTemplate = template ; return this ; } /** Registers the template that should be used for the drag placeholder. */ withPlaceholderTemplate ( template ) { this . _placeholderTemplate = template ; return this ; } /**
     * Sets an alternate drag root element. The root element is the element that will be moved as
     * the user is dragging. Passing an alternate root element is useful when trying to enable
     * dragging on an element that you might not have access to.
     */ withRootElement ( rootElement ) { const element = rootElement instanceof ElementRef ? rootElement . nativeElement : rootElement ; if ( element !== this . _rootElement ) { if ( this . _rootElement ) { this . _removeRootElementListeners ( this . _rootElement ) ; } element . addEventListener ( 'mousedown' , this . _pointerDown , activeEventListenerOptions ) ; element . addEventListener ( 'touchstart' , this . _pointerDown , passiveEventListenerOptions ) ; this . _rootElement = element ; } return this ; } /**
     * Element to which the draggable's position will be constrained.
     */ withBoundaryElement ( boundaryElement ) { this . _boundaryElement = boundaryElement instanceof ElementRef ? boundaryElement . nativeElement : boundaryElement ; return this ; } /** Removes the dragging functionality from the DOM element. */ dispose ( ) { this . _removeRootElementListeners ( this . _rootElement ) ; if ( this . isDragging ( ) ) { removeElement ( this . _rootElement ) ; } this . _destroyPreview ( ) ; this . _destroyPlaceholder ( ) ; this . _dragDropRegistry . removeDragItem ( this ) ; this . _removeSubscriptions ( ) ; this . beforeStarted . complete ( ) ; this . started . complete ( ) ; this . released . complete ( ) ; this . ended . complete ( ) ; this . entered . complete ( ) ; this . exited . complete ( ) ; this . dropped . complete ( ) ; this . _moveEvents . complete ( ) ; this . _handles = [ ] ; this . _boundaryElement = this . _rootElement = this . _placeholderTemplate = this . _previewTemplate = this . _nextSibling = null ! ; } /** Checks whether the element is currently being dragged. */ isDragging ( ) { return this . _hasStartedDragging && this . _dragDropRegistry . isDragging ( this ) ; } /** Resets a standalone drag item to its initial position. */ reset ( ) { this . _rootElement . style . transform = '' ; this . _activeTransform = { x : 0 , y : 0 } ; this . _passiveTransform = { x : 0 , y : 0 } ; } /** Unsubscribes from the global subscriptions. */ private _removeSubscriptions ( ) { this . _pointerMoveSubscription . unsubscribe ( ) ; this . _pointerUpSubscription . unsubscribe ( ) ; } /** Destroys the preview element and its ViewRef. */ private _destroyPreview ( ) { if ( this . _preview ) { removeElement ( this . _preview ) ; } if ( this . _previewRef ) { this . _previewRef . destroy ( ) ; } this . _preview = this . _previewRef = null ! ; } /** Destroys the placeholder element and its ViewRef. */ private _destroyPlaceholder ( ) { if ( this . _placeholder ) { removeElement ( this . _placeholder ) ; } if ( this . _placeholderRef ) { this . _placeholderRef . destroy ( ) ; } this . _placeholder = this . _placeholderRef = null ! ; } /** Handler for the `mousedown`/`touchstart` events. */ private _pointerDown = ( event ) => { this . beforeStarted . next ( ) ; if ( this . _handles . length ) { const targetHandle = this . _handles . find ( handle => { const element = handle . element . nativeElement ; const target = event . target ; return ! ! target && ( target === element || element . contains ( target as HTMLElement ) ) ; } ) ; if ( targetHandle && ! targetHandle . disabled && ! this . disabled ) { this . _initializeDragSequence ( targetHandle . element . nativeElement , event ) ; } } else if ( ! this . disabled ) { this . _initializeDragSequence ( this . _rootElement , event ) ; } } ; /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */ private _pointerMove = ( event ) => { if ( ! this . _hasStartedDragging ) { const pointerPosition = this . _getPointerPositionOnPage ( event ) ; const distanceX = Math . abs ( pointerPosition . x - this . _pickupPositionOnPage . x ) ; const distanceY = Math . abs ( pointerPosition . y - this . _pickupPositionOnPage . y ) ; if ( distanceX + distanceY >= this . _config . dragStartThreshold ) { this . _hasStartedDragging = true ; this . _ngZone . run ( ( ) => this . _startDragSequence ( event ) ) ; } return ; } if ( this . _boundaryElement ) { if ( ! this . _previewRect || ( ! this . _previewRect . width && ! this . _previewRect . height ) ) { this . _previewRect = ( this . _preview || this . _rootElement ) . getBoundingClientRect ( ) ; } } const constrainedPointerPosition = this . _getConstrainedPointerPosition ( event ) ; this . _hasMoved = true ; event . preventDefault ( ) ; this . _updatePointerDirectionDelta ( constrainedPointerPosition ) ; if ( this . dropContainer ) { this . _updateActiveDropContainer ( constrainedPointerPosition ) ; } else { const activeTransform = this . _activeTransform ; activeTransform . x = constrainedPointerPosition . x - this . _pickupPositionOnPage . x + this . _passiveTransform . x ; activeTransform . y = constrainedPointerPosition . y - this . _pickupPositionOnPage . y + this . _passiveTransform . y ; const transform = getTransform ( activeTransform . x , activeTransform . y ) ; this . _rootElement . style . transform = this . _initialTransform ? this . _initialTransform + ' ' + transform : transform ; if ( typeof SVGElement !== 'undefined' && this . _rootElement instanceof SVGElement ) { const appliedTransform = `translate(${ activeTransform . x } ${ activeTransform . y })` ; this . _rootElement . setAttribute ( 'transform' , appliedTransform ) ; } } if ( this . _moveEventSubscriptions > 0 ) { this . _ngZone . run ( ( ) => { this . _moveEvents . next ( { source : this , pointerPosition : constrainedPointerPosition , event , delta : this . _pointerDirectionDelta } ) ; } ) ; } } ; /** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */ private _pointerUp = ( event ) => { if ( ! this . _dragDropRegistry . isDragging ( this ) ) { return ; } this . _removeSubscriptions ( ) ; this . _dragDropRegistry . stopDragging ( this ) ; if ( this . _handles ) { this . _rootElement . style . webkitTapHighlightColor = this . _rootElementTapHighlight ; } if ( ! this . _hasStartedDragging ) { return ; } this . released . next ( { source : this } ) ; if ( ! this . dropContainer ) { this . _passiveTransform . x = this . _activeTransform . x ; this . _passiveTransform . y = this . _activeTransform . y ; this . _ngZone . run ( ( ) => this . ended . next ( { source : this } ) ) ; this . _dragDropRegistry . stopDragging ( this ) ; return ; } this . _animatePreviewToPlaceholder ( ) . then ( ( ) => { this . _cleanupDragArtifacts ( event ) ; this . _dragDropRegistry . stopDragging ( this ) ; } ) ; } ; /** Starts the dragging sequence. */ private _startDragSequence ( event ) { this . started . next ( { source : this } ) ; if ( isTouchEvent ( event ) ) { this . _lastTouchEventTime = Date . now ( ) ; } if ( this . dropContainer ) { const element = this . _rootElement ; this . _nextSibling = element . nextSibling ; const preview = this . _preview = this . _createPreviewElement ( ) ; const placeholder = this . _placeholder = this . _createPlaceholderElement ( ) ; element . style . display = 'none' ; this . _document . body . appendChild ( element . parentNode ! . replaceChild ( placeholder , element ) ) ; this . _document . body . appendChild ( preview ) ; this . dropContainer . start ( ) ; } } param referenceElement param event private _initializeDragSequence ( referenceElement , event ) { event . stopPropagation ( ) ; const isDragging = this . isDragging ( ) ; const isTouchSequence = isTouchEvent ( event ) ; const isAuxiliaryMouseButton = ! isTouchSequence && ( event as MouseEvent ) . button !== 0 ; const rootElement = this . _rootElement ; const isSyntheticEvent = ! isTouchSequence && this . _lastTouchEventTime && this . _lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date . now ( ) ; if ( event . target && ( event . target as HTMLElement ) . draggable && event . type === 'mousedown' ) { event . preventDefault ( ) ; } if ( isDragging || isAuxiliaryMouseButton || isSyntheticEvent ) { return ; } if ( this . _initialTransform == null ) { this . _initialTransform = this . _rootElement . style . transform || '' ; } if ( this . _handles . length ) { this . _rootElementTapHighlight = rootElement . style . webkitTapHighlightColor ; rootElement . style . webkitTapHighlightColor = 'transparent' ; } this . _toggleNativeDragInteractions ( ) ; this . _hasStartedDragging = this . _hasMoved = false ; this . _initialContainer = this . dropContainer ! ; this . _pointerMoveSubscription = this . _dragDropRegistry . pointerMove . subscribe ( this . _pointerMove ) ; this . _pointerUpSubscription = this . _dragDropRegistry . pointerUp . subscribe ( this . _pointerUp ) ; this . _scrollPosition = this . _viewportRuler . getViewportScrollPosition ( ) ; if ( this . _boundaryElement ) { this . _boundaryRect = this . _boundaryElement . getBoundingClientRect ( ) ; } this . _pickupPositionInElement = this . _previewTemplate ? { x : 0 , y : 0 } : this . _getPointerPositionInElement ( referenceElement , event ) ; const pointerPosition = this . _pickupPositionOnPage = this . _getPointerPositionOnPage ( event ) ; this . _pointerDirectionDelta = { x : 0 , y : 0 } ; this . _pointerPositionAtLastDirectionChange = { x : pointerPosition . x , y : pointerPosition . y } ; this . _dragDropRegistry . startDragging ( this , event ) ; } /** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */ private _cleanupDragArtifacts ( event ) { this . _rootElement . style . display = '' ; if ( this . _nextSibling ) { this . _nextSibling . parentNode ! . insertBefore ( this . _rootElement , this . _nextSibling ) ; } else { this . _initialContainer . element . nativeElement . appendChild ( this . _rootElement ) ; } this . _destroyPreview ( ) ; this . _destroyPlaceholder ( ) ; this . _boundaryRect = this . _previewRect = undefined ; this . _ngZone . run ( ( ) => { const container = this . dropContainer ! ; const currentIndex = container . getItemIndex ( this ) ; const { x , y } = this . _getPointerPositionOnPage ( event ) ; const isPointerOverContainer = container . _isOverContainer ( x , y ) ; this . ended . next ( { source : this } ) ; this . dropped . next ( { item : this , currentIndex , previousIndex : this . _initialContainer . getItemIndex ( this ) , container : container , previousContainer : this . _initialContainer , isPointerOverContainer } ) ; container . drop ( this , currentIndex , this . _initialContainer , isPointerOverContainer ) ; this . dropContainer = this . _initialContainer ; } ) ; } /**
     * Updates the item's position in its drop container, or moves it
     * into a new one, depending on its current drag position.
     */ private _updateActiveDropContainer ( { x , y } ) { let newContainer = this . dropContainer ! . _getSiblingContainerFromPosition ( this , x , y ) ; if ( ! newContainer && this . dropContainer !== this . _initialContainer && this . _initialContainer . _isOverContainer ( x , y ) ) { newContainer = this . _initialContainer ; } if ( newContainer ) { this . _ngZone . run ( ( ) => { this . exited . next ( { item : this , container : this . dropContainer ! } ) ; this . dropContainer ! . exit ( this ) ; this . entered . next ( { item : this , container : newContainer ! } ) ; this . dropContainer = newContainer ! ; this . dropContainer . enter ( this , x , y ) ; } ) ; } this . dropContainer ! . _sortItem ( this , x , y , this . _pointerDirectionDelta ) ; this . _preview . style . transform = getTransform ( x - this . _pickupPositionInElement . x , y - this . _pickupPositionInElement . y ) ; } /**
     * Creates the element that will be rendered next to the user's pointer
     * and will be used as a preview of the element that is being dragged.
     */ private _createPreviewElement ( ) { let preview ; if ( this . _previewTemplate ) { const viewRef = this . _viewContainerRef . createEmbeddedView ( this . _previewTemplate . templateRef , this . _previewTemplate . data ) ; preview = viewRef . rootNodes [ 0 ] ; this . _previewRef = viewRef ; preview . style . transform = getTransform ( this . _pickupPositionOnPage . x , this . _pickupPositionOnPage . y ) ; } else { const element = this . _rootElement ; const elementRect = element . getBoundingClientRect ( ) ; preview = deepCloneNode ( element ) ; preview . style . width = `${ elementRect . width }px` ; preview . style . height = `${ elementRect . height }px` ; preview . style . transform = getTransform ( elementRect . left , elementRect . top ) ; } extendStyles ( preview . style , { pointerEvents : 'none' , position : 'fixed' , top : '0' , left : '0' , zIndex : '1000' } ) ; toggleNativeDragInteractions ( preview , false ) ; preview . classList . add ( 'cdk-drag-preview' ) ; preview . setAttribute ( 'dir' , this . _dir ? this . _dir . value : 'ltr' ) ; return preview ; } returns private _animatePreviewToPlaceholder ( ) { if ( ! this . _hasMoved ) { return Promise . resolve ( ) ; } const placeholderRect = this . _placeholder . getBoundingClientRect ( ) ; this . _preview . classList . add ( 'cdk-drag-animating' ) ; this . _preview . style . transform = getTransform ( placeholderRect . left , placeholderRect . top ) ; const duration = getTransformTransitionDurationInMs ( this . _preview ) ; if ( duration === 0 ) { return Promise . resolve ( ) ; } return this . _ngZone . runOutsideAngular ( ( ) => { return new Promise ( resolve => { const handler = ( ( event ) => { if ( ! event || ( event . target === this . _preview && event . propertyName === 'transform' ) ) { this . _preview . removeEventListener ( 'transitionend' , handler ) ; resolve ( ) ; clearTimeout ( timeout ) ; } } ) as EventListenerOrEventListenerObject ; const timeout = setTimeout ( handler as Function , duration * 1.5 ) ; this . _preview . addEventListener ( 'transitionend' , handler ) ; } ) ; } ) ; } /** Creates an element that will be shown instead of the current element while dragging. */ private _createPlaceholderElement ( ) { let placeholder ; if ( this . _placeholderTemplate ) { this . _placeholderRef = this . _viewContainerRef . createEmbeddedView ( this . _placeholderTemplate . templateRef , this . _placeholderTemplate . data ) ; placeholder = this . _placeholderRef . rootNodes [ 0 ] ; } else { placeholder = deepCloneNode ( this . _rootElement ) ; } placeholder . classList . add ( 'cdk-drag-placeholder' ) ; return placeholder ; } param referenceElement param event private _getPointerPositionInElement ( referenceElement , event ) { const elementRect = this . _rootElement . getBoundingClientRect ( ) ; const handleElement = referenceElement === this . _rootElement ? null : referenceElement ; const referenceRect = handleElement ? handleElement . getBoundingClientRect ( ) : elementRect ; const point = isTouchEvent ( event ) ? event . targetTouches [ 0 ] : event ; const x = point . pageX - referenceRect . left - this . _scrollPosition . left ; const y = point . pageY - referenceRect . top - this . _scrollPosition . top ; return { x : referenceRect . left - elementRect . left + x , y : referenceRect . top - elementRect . top + y } ; } /** Determines the point of the page that was touched by the user. */ private _getPointerPositionOnPage ( event ) { const point = isTouchEvent ( event ) ? ( event . touches [ 0 ] || event . changedTouches [ 0 ] ) : event ; return { x : point . pageX - this . _scrollPosition . left , y : point . pageY - this . _scrollPosition . top } ; } /** Gets the pointer position on the page, accounting for any position constraints. */ private _getConstrainedPointerPosition ( event ) { const point = this . _getPointerPositionOnPage ( event ) ; const dropContainerLock = this . dropContainer ? this . dropContainer . lockAxis : null ; if ( this . lockAxis === 'x' || dropContainerLock === 'x' ) { point . y = this . _pickupPositionOnPage . y ; } else if ( this . lockAxis === 'y' || dropContainerLock === 'y' ) { point . x = this . _pickupPositionOnPage . x ; } if ( this . _boundaryRect ) { const { x : pickupX , y : pickupY } = this . _pickupPositionInElement ; const boundaryRect = this . _boundaryRect ; const previewRect = this . _previewRect ! ; const minY = boundaryRect . top + pickupY ; const maxY = boundaryRect . bottom - ( previewRect . height - pickupY ) ; const minX = boundaryRect . left + pickupX ; const maxX = boundaryRect . right - ( previewRect . width - pickupX ) ; point . x = clamp ( point . x , minX , maxX ) ; point . y = clamp ( point . y , minY , maxY ) ; } return point ; } /** Updates the current drag delta, based on the user's current pointer position on the page. */ private _updatePointerDirectionDelta ( pointerPositionOnPage ) { const { x , y } = pointerPositionOnPage ; const delta = this . _pointerDirectionDelta ; const positionSinceLastChange = this . _pointerPositionAtLastDirectionChange ; const changeX = Math . abs ( x - positionSinceLastChange . x ) ; const changeY = Math . abs ( y - positionSinceLastChange . y ) ; if ( changeX > this . _config . pointerDirectionChangeThreshold ) { delta . x = x > positionSinceLastChange . x ? 1 : - 1 ; positionSinceLastChange . x = x ; } if ( changeY > this . _config . pointerDirectionChangeThreshold ) { delta . y = y > positionSinceLastChange . y ? 1 : - 1 ; positionSinceLastChange . y = y ; } return delta ; } /** Toggles the native drag interactions, based on how many handles are registered. */ private _toggleNativeDragInteractions ( ) { if ( ! this . _rootElement || ! this . _handles ) { return ; } const shouldEnable = this . disabled || this . _handles . length > 0 ; if ( shouldEnable !== this . _nativeInteractionsEnabled ) { this . _nativeInteractionsEnabled = shouldEnable ; toggleNativeDragInteractions ( this . _rootElement , shouldEnable ) ; } } /** Removes the manually-added event listeners from the root element. */ private _removeRootElementListeners ( element ) { element . removeEventListener ( 'mousedown' , this . _pointerDown , activeEventListenerOptions ) ; element . removeEventListener ( 'touchstart' , this . _pointerDown , passiveEventListenerOptions ) ; } } /** Point on the page or within an element. */ interface Point { x : number ; y : number ; } param x param y function getTransform ( x , y ) { return `translate3d(${ Math . round ( x ) }px, ${ Math . round ( y ) }px, 0)` ; } /** Creates a deep clone of an element. */ function deepCloneNode ( node ) { const clone = node . cloneNode ( true ) as HTMLElement ; clone . removeAttribute ( 'id' ) ; return clone ; } /** Clamps a value between a minimum and a maximum. */ function clamp ( value , min , max ) { return Math . max ( min , Math . min ( max , value ) ) ; } param element function removeElement ( element ) { if ( element && element . parentNode ) { element . parentNode . removeChild ( element ) ; } } /** Determines whether an event is a touch event. */ function isTouchEvent ( event ) { return event . type . startsWith ( 'touch' ) ; }