UNPKG

@catull/igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1,202 lines (1,201 loc) 201 kB
import { __decorate, __metadata } from "tslib"; import { Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, NgZone, OnDestroy, OnInit, Output, Renderer2, ChangeDetectorRef, ViewContainerRef, AfterContentInit, TemplateRef, ContentChildren, QueryList } from '@angular/core'; import { animationFrameScheduler, fromEvent, interval, Subject } from 'rxjs'; import { takeUntil, throttle } from 'rxjs/operators'; import { IgxDragHandleDirective } from './drag-handle.directive'; import { DeprecateProperty } from '../../core/deprecateDecorators'; import { IgxDefaultDropStrategy } from './drag-drop.strategy'; export var RestrictDrag; (function (RestrictDrag) { RestrictDrag[RestrictDrag["VERTICALLY"] = 0] = "VERTICALLY"; RestrictDrag[RestrictDrag["HORIZONTALLY"] = 1] = "HORIZONTALLY"; RestrictDrag[RestrictDrag["NONE"] = 2] = "NONE"; })(RestrictDrag || (RestrictDrag = {})); export class IgxDragLocation { constructor(_pageX, _pageY) { this._pageX = _pageX; this._pageY = _pageY; this.pageX = parseFloat(_pageX); this.pageY = parseFloat(_pageY); } } let IgxDragDirective = class IgxDragDirective { constructor(cdr, element, viewContainer, zone, renderer) { this.cdr = cdr; this.element = element; this.viewContainer = viewContainer; this.zone = zone; this.renderer = renderer; this.ghostContext = null; /** * An @Input property that indicates when the drag should start. * By default the drag starts after the draggable element is moved by 5px. * ```html * <div igxDrag [dragTolerance]="100"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ this.dragTolerance = 5; /** * An @Input property that specifies if the base element should not be moved and a ghost element should be rendered that represents it. * By default it is set to `true`. * If it is set to `false` when dragging the base element is moved instead and no ghost elements are rendered. * ```html * <div igxDrag [ghost]="false"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ this.ghost = true; /** * Sets a custom class that will be added to the `ghostElement` element. * ```html * <div igxDrag [ghostClass]="'ghostElement'"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ this.ghostClass = ''; /** * @deprecated Please use custom base styling instead. * An @Input property that hides the draggable element. * By default it's set to false. * ```html * <div igxDrag [dragTolerance]="100" [hideBaseOnDrag]="'true'"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ this.hideBaseOnDrag = false; /** * @deprecated Please use provided transition functions in future. * An @Input property that enables/disables the draggable element animation * when the element is released. * By default it's set to false. * ```html * <div igxDrag [animateOnRelease]="'true'"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ this.animateOnRelease = false; /** * Event triggered when the draggable element drag starts. * ```html * <div igxDrag (dragStart)="onDragStart()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragStart(){ * alert("The drag has stared!"); * } * ``` * @memberof IgxDragDirective */ this.dragStart = new EventEmitter(); /** * Event triggered when the draggable element has been moved. * ```html * <div igxDrag (dragMove)="onDragMove()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragMove(){ * alert("The element has moved!"); * } * ``` * @memberof IgxDragDirective */ this.dragMove = new EventEmitter(); /** * Event triggered when the draggable element is released. * ```html * <div igxDrag (dragEnd)="onDragEnd()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragEnd(){ * alert("The drag has ended!"); * } * ``` * @memberof IgxDragDirective */ this.dragEnd = new EventEmitter(); /** * Event triggered when the draggable element is clicked. * ```html * <div igxDrag (dragClick)="onDragClick()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onDragClick(){ * alert("The element has been clicked!"); * } * ``` * @memberof IgxDragDirective */ this.dragClick = new EventEmitter(); /** * Event triggered when the drag ghost element is created. * ```html * <div igxDrag (ghostCreate)="ghostCreated()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public ghostCreated(){ * alert("The ghost has been created!"); * } * ``` * @memberof IgxDragDirective */ this.ghostCreate = new EventEmitter(); /** * Event triggered when the drag ghost element is created. * ```html * <div igxDrag (ghostDestroy)="ghostDestroyed()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public ghostDestroyed(){ * alert("The ghost has been destroyed!"); * } * ``` * @memberof IgxDragDirective */ this.ghostDestroy = new EventEmitter(); /** * Event triggered after the draggable element is released and after its animation has finished. * ```html * <div igxDrag (transitioned)="onMoveEnd()"> * <span>Drag Me!</span> * </div> * ``` * ```typescript * public onMoveEnd(){ * alert("The move has ended!"); * } * ``` * @memberof IgxDragDirective */ this.transitioned = new EventEmitter(); /** * @hidden */ this._visibility = 'visible'; /** * @hidden */ this.baseClass = true; /** * @hidden */ this.selectDisabled = false; /** * @hidden */ this.defaultReturnDuration = '0.5s'; /** * @hidden */ this.animInProgress = false; this._startX = 0; this._startY = 0; this._lastX = 0; this._lastY = 0; this._dragStarted = false; this._ghostHostX = 0; this._ghostHostY = 0; this._pointerDownId = null; this._clicked = false; this._lastDropArea = null; this._destroy = new Subject(); this._removeOnDestroy = true; } /** * An @Input property that specifies the offset of the dragged element relative to the mouse in pixels. * By default it's taking the relative position to the mouse when the drag started and keeps it the same. * ```html * <div #hostDiv></div> * <div igxDrag [ghostOffsetX]="0"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ set ghostOffsetX(value) { this._offsetX = parseInt(value, 10); } get ghostOffsetX() { return this._offsetX !== undefined ? this._offsetX : this._defaultOffsetX; } /** * An @Input property that specifies the offset of the dragged element relative to the mouse in pixels. * By default it's taking the relative position to the mouse when the drag started and keeps it the same. * ```html * <div #hostDiv></div> * <div igxDrag [ghostOffsetY]="0"> * <span>Drag Me!</span> * </div> * ``` * @memberof IgxDragDirective */ set ghostOffsetY(value) { this._offsetY = parseInt(value, 10); } get ghostOffsetY() { return this._offsetY !== undefined ? this._offsetY : this._defaultOffsetY; } /** * @deprecated Please use native angular ways of hiding it using custom to the base element styling for future versions. * Sets the visibility of the draggable element. * ```typescript * @ViewChild("myDrag" ,{read: IgxDragDirective}) * public myDrag: IgxDragDirective; * ngAfterViewInit(){ * this.myDrag.visible = false; * } * ``` */ set visible(bVisible) { this._visibility = bVisible ? 'visible' : 'hidden'; this.cdr.detectChanges(); } /** * Returns the visibility state of the draggable element. * ```typescript * @ViewChild("myDrag" ,{read: IgxDragDirective}) * public myDrag: IgxDragDirective; * ngAfterViewInit(){ * let dragVisibility = this.myDrag.visible; * } * ``` */ get visible() { return this._visibility === 'visible'; } /** * Gets the current location of the element relative to the page. */ get location() { return new IgxDragLocation(this.pageX, this.pageY); } /** * Gets the original location of the element before dragging started. */ get originLocation() { return new IgxDragLocation(this.baseOriginLeft, this.baseOriginTop); } /** * @hidden */ get pointerEventsEnabled() { return typeof PointerEvent !== 'undefined'; } /** * @hidden */ get touchEventsEnabled() { return 'ontouchstart' in window; } /** * @hidden */ get pageX() { if (this.ghost && this.ghostElement) { return this.ghostLeft; } return this.baseLeft; } /** * @hidden */ get pageY() { if (this.ghost && this.ghostElement) { return this.ghostTop; } return this.baseTop; } get baseLeft() { return this.element.nativeElement.getBoundingClientRect().left; } get baseTop() { return this.element.nativeElement.getBoundingClientRect().top; } get baseOriginLeft() { return this.baseLeft - this.getTransformX(this.element.nativeElement); } get baseOriginTop() { return this.baseTop - this.getTransformY(this.element.nativeElement); } set ghostLeft(pageX) { if (this.ghostElement) { // We need to take into account marginLeft, since top style does not include margin, but pageX includes the margin. const ghostMarginLeft = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.left = (pageX - ghostMarginLeft - this._ghostHostX) + 'px'; } } get ghostLeft() { return parseInt(this.ghostElement.style.left, 10) + this._ghostHostX; } set ghostTop(pageY) { if (this.ghostElement) { // We need to take into account marginTop, since top style does not include margin, but pageY includes the margin. const ghostMarginTop = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.top = (pageY - ghostMarginTop - this._ghostHostY) + 'px'; } } get ghostTop() { return parseInt(this.ghostElement.style.top, 10) + this._ghostHostY; } /** * @hidden */ ngAfterContentInit() { if (!this.dragHandles || !this.dragHandles.length) { // Set user select none to the whole draggable element if no drag handles are defined. this.selectDisabled = true; } // Bind events this.zone.runOutsideAngular(() => { const targetElements = this.dragHandles && this.dragHandles.length ? this.dragHandles.map((item) => item.element.nativeElement) : [this.element.nativeElement]; targetElements.forEach((element) => { if (this.pointerEventsEnabled) { fromEvent(element, 'pointerdown').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); fromEvent(element, 'pointermove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(element, 'pointerup').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); if (!this.ghost) { // Do not bind `lostpointercapture` to the target, because we will bind it on the ghost later. fromEvent(element, 'lostpointercapture').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerLost(res)); } } else if (this.touchEventsEnabled) { fromEvent(element, 'touchstart').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); } else { // We don't have pointer events and touch events. Use then mouse events. fromEvent(element, 'mousedown').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerDown(res)); } }); // We should bind to document events only once when there are no pointer events. if (!this.pointerEventsEnabled && this.touchEventsEnabled) { fromEvent(document.defaultView, 'touchmove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(document.defaultView, 'touchend').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } else if (!this.pointerEventsEnabled) { fromEvent(document.defaultView, 'mousemove').pipe(throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy)).subscribe((res) => this.onPointerMove(res)); fromEvent(document.defaultView, 'mouseup').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } this.element.nativeElement.addEventListener('transitionend', (args) => { this.onTransitionEnd(args); }); }); // Set transition duration to 0s. This also helps with setting `visibility: hidden` to the base to not lag. this.element.nativeElement.style.transitionDuration = '0.0s'; } /** * @hidden */ ngOnDestroy() { this._destroy.next(true); this._destroy.complete(); if (this.ghost && this.ghostElement && this._removeOnDestroy) { this.ghostElement.parentNode.removeChild(this.ghostElement); this.ghostElement = null; } } /** * Sets desired location of the base element or ghost element if rended relative to the document. * @param newLocation New location that should be applied. It is advised to get new location using getBoundingClientRects() + scroll. */ setLocation(newLocation) { // We do not subtract marginLeft and marginTop here because here we calculate deltas. if (this.ghost && this.ghostElement) { const offsetHostX = this.ghostHost ? this.ghostHostOffsetLeft(this.ghostHost) : 0; const offsetHostY = this.ghostHost ? this.ghostHostOffsetTop(this.ghostHost) : 0; this.ghostLeft = newLocation.pageX - offsetHostX + this.getWindowScrollLeft(); this.ghostTop = newLocation.pageY - offsetHostY + this.getWindowScrollTop(); } else if (!this.ghost) { const deltaX = newLocation.pageX - this.pageX; const deltaY = newLocation.pageY - this.pageY; const transformX = this.getTransformX(this.element.nativeElement); const transformY = this.getTransformY(this.element.nativeElement); this.setTransformXY(transformX + deltaX, transformY + deltaY); } this._startX = this.baseLeft; this._startY = this.baseTop; } /** * Animates the base or ghost element depending on the `ghost` input to its initial location. * If `ghost` is true but there is not ghost rendered, it will be created and animated. * If the base element has changed its DOM position its initial location will be changed accordingly. * @param customAnimArgs Custom transition properties that will be applied when performing the transition. * @param startLocation Start location from where the transition should start. */ transitionToOrigin(customAnimArgs, startLocation) { if ((!!startLocation && startLocation.pageX === this.baseOriginLeft && startLocation.pageY === this.baseOriginLeft) || (!startLocation && this.ghost && !this.ghostElement)) { return; } if (!!startLocation && startLocation.pageX !== this.pageX && startLocation.pageY !== this.pageY) { if (this.ghost && !this.ghostElement) { this._startX = startLocation.pageX; this._startY = startLocation.pageY; this._ghostStartX = this._startX; this._ghostStartY = this._startY; this.createGhost(this._startX, this._startY); } this.setLocation(startLocation); } this.animInProgress = true; // Use setTimeout because we need to be sure that the element is positioned first correctly if there is start location. setTimeout(() => { if (this.ghost) { this.ghostElement.style.transitionProperty = 'top, left'; this.ghostElement.style.transitionDuration = customAnimArgs && customAnimArgs.duration ? customAnimArgs.duration + 's' : this.defaultReturnDuration; this.ghostElement.style.transitionTimingFunction = customAnimArgs && customAnimArgs.timingFunction ? customAnimArgs.timingFunction : ''; this.ghostElement.style.transitionDelay = customAnimArgs && customAnimArgs.delay ? customAnimArgs.delay + 's' : ''; this.setLocation(new IgxDragLocation(this.baseLeft, this.baseTop)); } else if (!this.ghost) { this.element.nativeElement.style.transitionProperty = 'transform'; this.element.nativeElement.style.transitionDuration = customAnimArgs && customAnimArgs.duration ? customAnimArgs.duration + 's' : this.defaultReturnDuration; this.element.nativeElement.style.transitionTimingFunction = customAnimArgs && customAnimArgs.timingFunction ? customAnimArgs.timingFunction : ''; this.element.nativeElement.style.transitionDelay = customAnimArgs && customAnimArgs.delay ? customAnimArgs.delay + 's' : ''; this._startX = this.baseLeft; this._startY = this.baseTop; this.setTransformXY(0, 0); } }, 0); } /** * Animates the base or ghost element to a specific target location or other element using transition. * If `ghost` is true but there is not ghost rendered, it will be created and animated. * It is recommended to use 'getBoundingClientRects() + pageScroll' when determining desired location. * @param target Target that the base or ghost will transition to. It can be either location in the page or another HTML element. * @param customAnimArgs Custom transition properties that will be applied when performing the transition. * @param startLocation Start location from where the transition should start. */ transitionTo(target, customAnimArgs, startLocation) { if (!!startLocation && this.ghost && !this.ghostElement) { this._startX = startLocation.pageX; this._startY = startLocation.pageY; this._ghostStartX = this._startX; this._ghostStartY = this._startY; } else if (!!startLocation && (!this.ghost || this.ghostElement)) { this.setLocation(startLocation); } else if (this.ghost && !this.ghostElement) { this._startX = this.baseLeft; this._startY = this.baseTop; this._ghostStartX = this._startX + this.getWindowScrollLeft(); this._ghostStartY = this._startY + this.getWindowScrollTop(); } if (this.ghost && !this.ghostElement) { this.createGhost(this._startX, this._startY); } this.animInProgress = true; // Use setTimeout because we need to be sure that the element is positioned first correctly if there is start location. setTimeout(() => { const movedElem = this.ghost ? this.ghostElement : this.element.nativeElement; movedElem.style.transitionProperty = this.ghost && this.ghostElement ? 'left, top' : 'transform'; movedElem.style.transitionDuration = customAnimArgs && customAnimArgs.duration ? customAnimArgs.duration + 's' : this.defaultReturnDuration; movedElem.style.transitionTimingFunction = customAnimArgs && customAnimArgs.timingFunction ? customAnimArgs.timingFunction : ''; movedElem.style.transitionDelay = customAnimArgs && customAnimArgs.delay ? customAnimArgs.delay + 's' : ''; if (target instanceof IgxDragLocation) { this.setLocation(new IgxDragLocation(target.pageX, target.pageY)); } else { const targetRects = target.nativeElement.getBoundingClientRect(); this.setLocation(new IgxDragLocation(targetRects.left - this.getWindowScrollLeft(), targetRects.top - this.getWindowScrollTop())); } }, 0); } /** * @hidden * Method bound to the PointerDown event of the base element igxDrag is initialized. * @param event PointerDown event captured */ onPointerDown(event) { this._clicked = true; this._pointerDownId = event.pointerId; // Set pointer capture so we detect pointermove even if mouse is out of bounds until ghostElement is created. const handleFound = this.dragHandles.find(handle => handle.element.nativeElement === event.currentTarget); const targetElement = handleFound ? handleFound.element.nativeElement : this.element.nativeElement; if (this.pointerEventsEnabled) { targetElement.setPointerCapture(this._pointerDownId); } else { targetElement.focus(); event.preventDefault(); } if (this.pointerEventsEnabled || !this.touchEventsEnabled) { // Check first for pointer events or non touch, because we can have pointer events and touch events at once. this._startX = event.pageX; this._startY = event.pageY; } else if (this.touchEventsEnabled) { this._startX = event.touches[0].pageX; this._startY = event.touches[0].pageY; } this._defaultOffsetX = this.baseLeft - this._startX + this.getWindowScrollLeft(); this._defaultOffsetY = this.baseTop - this._startY + this.getWindowScrollTop(); this._ghostStartX = this._startX + this.ghostOffsetX; this._ghostStartY = this._startY + this.ghostOffsetY; this._lastX = this._startX; this._lastY = this._startY; } /** * @hidden * Perform drag move logic when dragging and dispatching events if there is igxDrop under the pointer. * This method is bound at first at the base element. * If dragging starts and after the ghostElement is rendered the pointerId is reassigned it. Then this method is bound to it. * @param event PointerMove event captured */ onPointerMove(event) { if (this._clicked) { let pageX, pageY; if (this.pointerEventsEnabled || !this.touchEventsEnabled) { // Check first for pointer events or non touch, because we can have pointer events and touch events at once. pageX = event.pageX; pageY = event.pageY; } else if (this.touchEventsEnabled) { pageX = event.touches[0].pageX; pageY = event.touches[0].pageY; // Prevent scrolling on touch while dragging event.preventDefault(); } const totalMovedX = pageX - this._startX; const totalMovedY = pageY - this._startY; if (!this._dragStarted && (Math.abs(totalMovedX) > this.dragTolerance || Math.abs(totalMovedY) > this.dragTolerance)) { const dragStartArgs = { originalEvent: event, owner: this, startX: pageX - totalMovedX, startY: pageY - totalMovedY, pageX: pageX, pageY: pageY, cancel: false }; this.zone.run(() => { this.dragStart.emit(dragStartArgs); }); if (!dragStartArgs.cancel) { this._dragStarted = true; if (this.ghost) { // We moved enough so ghostElement can be rendered and actual dragging to start. // When creating it will take into account any offset set by the user by default. this.createGhost(pageX, pageY); } else if (this._offsetX !== undefined || this._offsetY !== undefined) { // There is no need for ghost, but we will need to position initially the base element to reflect any offset. const transformX = (this._offsetX !== undefined ? this._offsetX - this._defaultOffsetX : 0) + this.getTransformX(this.element.nativeElement); const transformY = (this._offsetY !== undefined ? this._offsetY - this._defaultOffsetY : 0) + this.getTransformY(this.element.nativeElement); this.setTransformXY(transformX, transformY); } } else { return; } } else if (!this._dragStarted) { return; } const moveArgs = { originalEvent: event, owner: this, startX: this._startX, startY: this._startY, pageX: this._lastX, pageY: this._lastY, nextPageX: pageX, nextPageY: pageY, cancel: false }; this.dragMove.emit(moveArgs); const setPageX = moveArgs.nextPageX; const setPageY = moveArgs.nextPageY; const updatedMovedX = setPageX - this._startX; const updatedMovedY = setPageY - this._startY; if (!moveArgs.cancel) { if (this.ghost) { this.ghostLeft = this._ghostStartX + updatedMovedX; this.ghostTop = this._ghostStartY + updatedMovedY; } else { const lastMovedX = setPageX - this._lastX; const lastMovedY = setPageY - this._lastY; const translateX = this.getTransformX(this.element.nativeElement) + lastMovedX; const translateY = this.getTransformY(this.element.nativeElement) + lastMovedY; this.setTransformXY(translateX, translateY); } this.dispatchDragEvents(pageX, pageY, event); } this._lastX = setPageX; this._lastY = setPageY; } } /** * @hidden * Perform drag end logic when releasing the ghostElement and dispatching drop event if igxDrop is under the pointer. * This method is bound at first at the base element. * If dragging starts and after the ghostElement is rendered the pointerId is reassigned to it. Then this method is bound to it. * @param event PointerUp event captured */ onPointerUp(event) { if (!this._clicked) { return; } let pageX, pageY; if (this.pointerEventsEnabled || !this.touchEventsEnabled) { // Check first for pointer events or non touch, because we can have pointer events and touch events at once. pageX = event.pageX; pageY = event.pageY; } else if (this.touchEventsEnabled) { pageX = event.touches[0].pageX; pageY = event.touches[0].pageY; // Prevent scrolling on touch while dragging event.preventDefault(); } const eventArgs = { originalEvent: event, owner: this, startX: this._startX, startY: this._startY, pageX: pageX, pageY: pageY }; this._pointerDownId = null; this._clicked = false; if (this._dragStarted) { if (this._lastDropArea && this._lastDropArea !== this.element.nativeElement) { this.dispatchDropEvent(event.pageX, event.pageY, event); } else if (this.animateOnRelease) { this.transitionToOrigin(); } this.zone.run(() => { this.dragEnd.emit(eventArgs); }); if (!this.animInProgress) { this.onTransitionEnd(null); } } else { // Trigger our own click event because when there is no ghost, native click cannot be prevented when dragging. this.zone.run(() => { this.dragClick.emit(eventArgs); }); } } /** * @hidden * Execute this method whe the pointer capture has been lost. * This means that during dragging the user has performed other action like right clicking and then clicking somewhere else. * This method will ensure that the drag state is being reset in this case as if the user released the dragged element. * @param event Event captured */ onPointerLost(event) { if (!this._clicked) { return; } const eventArgs = { originalEvent: event, owner: this, startX: this._startX, startY: this._startY, pageX: event.pageX, pageY: event.pageY }; this._pointerDownId = null; this._clicked = false; if (this._dragStarted) { this.zone.run(() => { this.dragEnd.emit(eventArgs); }); if (this.animateOnRelease) { this.transitionToOrigin(); } else if (!this.animInProgress) { this.onTransitionEnd(null); } } } /** * @hidden * Create ghost element - if a Node object is provided it creates a clone of that node, * otherwise it clones the host element. * Bind all needed events. * @param pageX Latest pointer position on the X axis relative to the page. * @param pageY Latest pointer position on the Y axis relative to the page. * @param node The Node object to be cloned. */ createGhost(pageX, pageY, node = null) { if (!this.ghost) { return; } let dynamicGhostRef; if (this.ghostTemplate) { dynamicGhostRef = this.viewContainer.createEmbeddedView(this.ghostTemplate, this.ghostContext); this.ghostElement = dynamicGhostRef.rootNodes[0]; } else { this.ghostElement = node ? node.cloneNode(true) : this.element.nativeElement.cloneNode(true); } const totalMovedX = pageX - this._startX; const totalMovedY = pageY - this._startY; this._ghostHostX = this.ghostHost ? this.ghostHostOffsetLeft(this.ghostHost) : 0; this._ghostHostY = this.ghostHost ? this.ghostHostOffsetTop(this.ghostHost) : 0; this.ghostElement.style.transitionDuration = '0.0s'; this.ghostElement.style.position = 'absolute'; if (this.ghostClass) { this.renderer.addClass(this.ghostElement, this.ghostClass); } const createEventArgs = { owner: this, ghostElement: this.ghostElement, cancel: false }; this.ghostCreate.emit(createEventArgs); if (createEventArgs.cancel) { this.ghostElement = null; if (this.ghostTemplate && dynamicGhostRef) { dynamicGhostRef.destroy(); } return; } if (this.ghostHost) { this.ghostHost.appendChild(this.ghostElement); } else { document.body.appendChild(this.ghostElement); } const ghostMarginLeft = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); const ghostMarginTop = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); this.ghostElement.style.left = (this._ghostStartX - ghostMarginLeft + totalMovedX - this._ghostHostX) + 'px'; this.ghostElement.style.top = (this._ghostStartY - ghostMarginTop + totalMovedY - this._ghostHostX) + 'px'; if (this.pointerEventsEnabled) { // The ghostElement takes control for moving and dragging after it has been rendered. if (this._pointerDownId !== null) { this.ghostElement.setPointerCapture(this._pointerDownId); } this.ghostElement.addEventListener('pointermove', (args) => { this.onPointerMove(args); }); this.ghostElement.addEventListener('pointerup', (args) => { this.onPointerUp(args); }); this.ghostElement.addEventListener('lostpointercapture', (args) => { this.onPointerLost(args); }); } // Transition animation when the ghostElement is released and it returns to it's original position. this.ghostElement.addEventListener('transitionend', (args) => { this.onTransitionEnd(args); }); // Hide the base after the ghostElement is created, because otherwise the ghostElement will be not visible. if (this.hideBaseOnDrag) { this.visible = false; } this.cdr.detectChanges(); } /** * @hidden * Dispatch custom igxDragEnter/igxDragLeave events based on current pointer position and if drop area is under. */ dispatchDragEvents(pageX, pageY, originalEvent) { let topDropArea; const customEventArgs = { startX: this._startX, startY: this._startY, pageX: pageX, pageY: pageY, owner: this, originalEvent: originalEvent }; const elementsFromPoint = this.getElementsAtPoint(pageX, pageY); for (let i = 0; i < elementsFromPoint.length; i++) { if (elementsFromPoint[i].getAttribute('droppable') === 'true' && elementsFromPoint[i] !== this.ghostElement && elementsFromPoint[i] !== this.element.nativeElement) { topDropArea = elementsFromPoint[i]; break; } } if (topDropArea && (!this._lastDropArea || (this._lastDropArea && this._lastDropArea !== topDropArea))) { if (this._lastDropArea) { this.dispatchEvent(this._lastDropArea, 'igxDragLeave', customEventArgs); } this._lastDropArea = topDropArea; this.dispatchEvent(this._lastDropArea, 'igxDragEnter', customEventArgs); } else if (!topDropArea && this._lastDropArea) { this.dispatchEvent(this._lastDropArea, 'igxDragLeave', customEventArgs); this._lastDropArea = null; return; } if (topDropArea) { this.dispatchEvent(topDropArea, 'igxDragOver', customEventArgs); } } /** * @hidden * Dispatch custom igxDrop event based on current pointer position if there is last recorder drop area under the pointer. * Last recorder drop area is updated in @dispatchDragEvents method. */ dispatchDropEvent(pageX, pageY, originalEvent) { const eventArgs = { startX: this._startX, startY: this._startY, pageX: pageX, pageY: pageY, owner: this, originalEvent: originalEvent }; this.dispatchEvent(this._lastDropArea, 'igxDrop', eventArgs); this.dispatchEvent(this._lastDropArea, 'igxDragLeave', eventArgs); this._lastDropArea = null; } /** * @hidden */ onTransitionEnd(event) { if ((!this._dragStarted && !this.animInProgress) || this._clicked) { // Return if no dragging started and there is no animation in progress. return; } if (this.ghost && this.ghostElement) { this._ghostStartX = this.baseLeft + this.getWindowScrollLeft(); this._ghostStartY = this.baseTop + this.getWindowScrollTop(); const ghostDestroyArgs = { owner: this, ghostElement: this.ghostElement, cancel: false }; this.ghostDestroy.emit(ghostDestroyArgs); if (ghostDestroyArgs.cancel) { return; } if (this.hideBaseOnDrag) { this.visible = true; } this.ghostElement.parentNode.removeChild(this.ghostElement); this.ghostElement = null; } else if (!this.ghost) { this.element.nativeElement.style.transitionProperty = ''; this.element.nativeElement.style.transitionDuration = '0.0s'; this.element.nativeElement.style.transitionTimingFunction = ''; this.element.nativeElement.style.transitionDelay = ''; } this.animInProgress = false; this._dragStarted = false; // Execute transitioned after everything is reset so if the user sets new location on the base now it would work as expected. this.zone.run(() => { this.transitioned.emit({ originalEvent: event, owner: this, startX: this._startX, startY: this._startY, pageX: this._startX, pageY: this._startY }); }); } /** * @hidden */ getElementsAtPoint(pageX, pageY) { // correct the coordinates with the current scroll position, because // document.elementsFromPoint consider position within the current viewport // window.pageXOffset == window.scrollX; // always true // using window.pageXOffset for IE9 compatibility const viewPortX = pageX - window.pageXOffset; const viewPortY = pageY - window.pageYOffset; if (document['msElementsFromPoint']) { // Edge and IE special snowflakes const elements = document['msElementsFromPoint'](viewPortX, viewPortY); return elements === null ? [] : elements; } else { // Other browsers like Chrome, Firefox, Opera return document.elementsFromPoint(viewPortX, viewPortY); } } /** * @hidden */ dispatchEvent(target, eventName, eventArgs) { // This way is IE11 compatible. const dragLeaveEvent = document.createEvent('CustomEvent'); dragLeaveEvent.initCustomEvent(eventName, false, false, eventArgs); target.dispatchEvent(dragLeaveEvent); // Otherwise can be used `target.dispatchEvent(new CustomEvent(eventName, eventArgs));` } getTransformX(elem) { let posX = 0; if (elem.style.transform) { const matrix = elem.style.transform; const values = matrix ? matrix.match(/-?[\d\.]+/g) : undefined; posX = values ? Number(values[1]) : 0; } return posX; } getTransformY(elem) { let posY = 0; if (elem.style.transform) { const matrix = elem.style.transform; const values = matrix ? matrix.match(/-?[\d\.]+/g) : undefined; posY = values ? Number(values[2]) : 0; } return posY; } /** Method setting transformation to the base draggable element. */ setTransformXY(x, y) { this.element.nativeElement.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0px)'; } getWindowScrollTop() { return window.scrollY ? window.scrollY : (window.pageYOffset ? window.pageYOffset : 0); } getWindowScrollLeft() { return window.scrollX ? window.scrollX : (window.pageXOffset ? window.pageXOffset : 0); } ghostHostOffsetLeft(ghostHost) { const ghostPosition = document.defaultView.getComputedStyle(ghostHost).getPropertyValue('position'); if (ghostPosition === 'static' && ghostHost.offsetParent && ghostHost.offsetParent === document.body) { return 0; } else if (ghostPosition === 'static' && ghostHost.offsetParent) { return ghostHost.offsetParent.getBoundingClientRect().left - this.getWindowScrollLeft(); } return ghostHost.getBoundingClientRect().left - this.getWindowScrollLeft(); } ghostHostOffsetTop(ghostHost) { const ghostPosition = document.defaultView.getComputedStyle(ghostHost).getPropertyValue('position'); if (ghostPosition === 'static' && ghostHost.offsetParent && ghostHost.offsetParent === document.body) { return 0; } else if (ghostPosition === 'static' && ghostHost.offsetParent) { return ghostHost.offsetParent.getBoundingClientRect().top - this.getWindowScrollTop(); } return ghostHost.getBoundingClientRect().top - this.getWindowScrollTop(); } }; IgxDragDirective.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: ElementRef }, { type: ViewContainerRef }, { type: NgZone }, { type: Renderer2 } ]; __decorate([ Input('igxDrag'), __metadata("design:type", Object) ], IgxDragDirective.prototype, "data", void 0); __decorate([ Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragTolerance", void 0); __decorate([ Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragChannel", void 0); __decorate([ Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "ghost", void 0); __decorate([ Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "ghostClass", void 0); __decorate([ DeprecateProperty(`'hideBaseOnDrag' @Input property is deprecated and will be removed in future major versions. Alternatives to it are using the new no ghost dragging and custom base styling.`), Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "hideBaseOnDrag", void 0); __decorate([ DeprecateProperty(`'animateOnRelease' @Input property is deprecated and will be removed in future major versions. Please use 'transitionToOrigin' or 'transitionTo' methods instead.`), Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "animateOnRelease", void 0); __decorate([ Input(), __metadata("design:type", TemplateRef) ], IgxDragDirective.prototype, "ghostTemplate", void 0); __decorate([ Input(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "ghostHost", void 0); __decorate([ Input(), __metadata("design:type", Object), __metadata("design:paramtypes", [Object]) ], IgxDragDirective.prototype, "ghostOffsetX", null); __decorate([ Input(), __metadata("design:type", Object), __metadata("design:paramtypes", [Object]) ], IgxDragDirective.prototype, "ghostOffsetY", null); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragStart", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragMove", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragEnd", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "dragClick", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "ghostCreate", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "ghostDestroy", void 0); __decorate([ Output(), __metadata("design:type", Object) ], IgxDragDirective.prototype, "transitioned", void 0); __decorate([ ContentChildren(IgxDragHandleDirective), __metadata("design:type", QueryList) ], IgxDragDirective.prototype, "dragHandles", void 0); __decorate([ HostBinding('style.visibility'), __metadata("design:type", Object) ], IgxDragDirective.prototype, "_visibility", void 0); __decorate([ HostBinding('class.igx-drag'), __metadata("design:type", Object) ], IgxDragDirective.prototype, "baseClass", void 0); __decorate([ HostBinding('class.igx-drag--select-disabled'), __metadata("design:type", Object) ], IgxDragDirective.prototype, "selectDisabled", void 0); __decorate([ DeprecateProperty(`'visible' @Input property is deprecated and will be removed in future major versions. Please use native angular ways of hiding the base element using styling.`), __metadata("design:type", Object), __metadata("design:paramtypes", [Object]) ], IgxDragDirective.prototype, "visible", null); IgxDragDirective = __decorate([ Directive({ exportAs: 'drag', selector: '[igxDrag]' }), __metadata("design:paramtypes", [ChangeDetectorRef, ElementRef, ViewContainerRef, NgZone, Renderer2]) ], IgxDragDirective); export { IgxDragDirective }; let IgxDropDirective = class IgxDropDirective { constructor(element, _renderer, _zone) { this.element = element; this._renderer = _renderer; this._zone = _zone; /** Event triggered when dragged element enters the area of the element. * ```html * <div class="cageArea" igxDrop (enter)="dragEnter()" (igxDragEnter)="onDragCageEnter()" (igxDragLeave)="onDragCageLeave()"> * </div> * ``` * ```typescript * public dragEnter(){ * alert("A draggable element has entered the chip area!"); * } * ``` * @memberof IgxDropDirective */ this.enter = new EventEmitter(); /** Event triggered when dragged element enters the area of the element. * ```html * <div class="cageArea" igxDrop (enter)="dragEnter()" (igxDragEnter)="onDragCageEnter()" (igxDragLeave)="onDragCageLeave()"> * </div> * ``` * ```typescript * public dragEnter(){ * alert("A draggable element has entered the chip area!"); * } * ``` * @memberof IgxDropDirective */ this.over = new EventEmitter(); /** Event triggered when dragged element leaves the area of the element. * ```html * <div class="cageArea" igxDrop (leave)="dragLeave()" (igxDragEnter)="onDragCageEnter()" (igxDragLeave)="onDragCageLeave()"> * </div> * ``` * ```typescript * public dragLeave(){ * alert("A draggable element has left the chip area!"); * } * ``` * @memberof IgxDropDirective */ this.leave = new EventEmitter(); /** Event triggered when dragged element is dropped in the area of the element. * Since the `igxDrop` has default logic that appends the dropped element as a child, it can be canceled here. * To cancel the default logic the `cancel` property of the event needs to be set to true. * ```html * <div class="cageArea" igxDrop (dropped)="dragDrop()" (igxDragEnter)="onDragCageEnter()" (igxDragLeave)="onDragCageLeave()"> * </div> * ``` * ```typescript * public dragDrop(){ * alert("A draggable element has been dropped in the chip area!"); * } * ``` * @memberof IgxDropDirective */ this.dropped = new EventEmitter(); /** * @hidden */ this.droppable = true; /** * @hidden