@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
JavaScript
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