Skip to content

Commit 8439d5e

Browse files
committed
feat(IgxDrag/IgxDrop): Add support for multiple handles and update Column Moving directive. Fix performance due to dragMove event. #5319
1 parent dd274c8 commit 8439d5e

File tree

2 files changed

+67
-62
lines changed

2 files changed

+67
-62
lines changed

projects/igniteui-angular/src/lib/directives/drag-drop/drag-drop.directive.ts

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
Output,
1313
Renderer2,
1414
ChangeDetectorRef,
15-
ContentChild,
1615
ViewContainerRef,
1716
AfterContentInit,
18-
TemplateRef
17+
TemplateRef,
18+
ContentChildren,
19+
QueryList
1920
} from '@angular/core';
2021
import { animationFrameScheduler, fromEvent, interval, Subject } from 'rxjs';
2122
import { takeUntil, throttle } from 'rxjs/operators';
@@ -454,8 +455,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
454455
/**
455456
* @hidden
456457
*/
457-
@ContentChild(IgxDragHandleDirective, { static: false })
458-
public dragHandle: IgxDragHandleDirective;
458+
@ContentChildren(IgxDragHandleDirective)
459+
public dragHandles: QueryList<IgxDragHandleDirective>;
459460

460461
/**
461462
* @hidden
@@ -525,10 +526,11 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
525526
/**
526527
* @hidden
527528
*/
528-
public set ghostLeft(val: number) {
529+
public set ghostLeft(pageX: number) {
529530
requestAnimationFrame(() => {
530531
if (this.dragGhost) {
531-
this.dragGhost.style.left = val + 'px';
532+
// If ghost host is defined it needs to be taken into account.
533+
this.dragGhost.style.left = (pageX - this._dragGhostHostX) + 'px';
532534
}
533535
});
534536
}
@@ -537,16 +539,17 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
537539
* @hidden
538540
*/
539541
public get ghostLeft() {
540-
return parseInt(this.dragGhost.style.left, 10);
542+
return parseInt(this.dragGhost.style.left, 10) + this._dragGhostHostX;
541543
}
542544

543545
/**
544546
* @hidden
545547
*/
546-
public set ghostTop(val: number) {
548+
public set ghostTop(pageY: number) {
547549
requestAnimationFrame(() => {
548550
if (this.dragGhost) {
549-
this.dragGhost.style.top = val + 'px';
551+
// If ghost host is defined it needs to be taken into account.
552+
this.dragGhost.style.top = (pageY - this._dragGhostHostY) + 'px';
550553
}
551554
});
552555
}
@@ -555,7 +558,7 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
555558
* @hidden
556559
*/
557560
public get ghostTop() {
558-
return parseInt(this.dragGhost.style.top, 10);
561+
return parseInt(this.dragGhost.style.top, 10) + this._dragGhostHostY;
559562
}
560563

561564
/**
@@ -629,41 +632,47 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
629632
* @hidden
630633
*/
631634
public ngAfterContentInit() {
632-
const targetElement = this.dragHandle ? this.dragHandle.element.nativeElement : this.element.nativeElement;
633635
this.zone.runOutsideAngular(() => {
634-
if (this.pointerEventsEnabled) {
635-
fromEvent(targetElement, 'pointerdown').pipe(takeUntil(this._destroy))
636-
.subscribe((res) => this.onPointerDown(res));
637-
638-
fromEvent(targetElement, 'pointermove').pipe(
639-
throttle(() => interval(0, animationFrameScheduler)),
640-
takeUntil(this._destroy)
641-
).subscribe((res) => this.onPointerMove(res));
642-
643-
fromEvent(targetElement, 'pointerup').pipe(takeUntil(this._destroy))
644-
.subscribe((res) => this.onPointerUp(res));
645-
646-
if (!this.renderGhost) {
647-
// Do not bind `lostpointercapture` to the target, because we will bind it on the ghost later.
648-
fromEvent(targetElement, 'lostpointercapture').pipe(takeUntil(this._destroy))
649-
.subscribe((res) => this.onPointerLost(res));
636+
const targetElements = this.dragHandles && this.dragHandles.length ?
637+
this.dragHandles.map((item) => item.element.nativeElement) : [this.element.nativeElement];
638+
targetElements.forEach((element) => {
639+
if (this.pointerEventsEnabled) {
640+
fromEvent(element, 'pointerdown').pipe(takeUntil(this._destroy))
641+
.subscribe((res) => this.onPointerDown(res));
642+
643+
fromEvent(element, 'pointermove').pipe(
644+
throttle(() => interval(0, animationFrameScheduler)),
645+
takeUntil(this._destroy)
646+
).subscribe((res) => this.onPointerMove(res));
647+
648+
fromEvent(element, 'pointerup').pipe(takeUntil(this._destroy))
649+
.subscribe((res) => this.onPointerUp(res));
650+
651+
if (!this.renderGhost) {
652+
// Do not bind `lostpointercapture` to the target, because we will bind it on the ghost later.
653+
fromEvent(element, 'lostpointercapture').pipe(takeUntil(this._destroy))
654+
.subscribe((res) => this.onPointerLost(res));
655+
}
656+
} else if (this.touchEventsEnabled) {
657+
fromEvent(element, 'touchstart').pipe(takeUntil(this._destroy))
658+
.subscribe((res) => this.onPointerDown(res));
659+
} else {
660+
// We don't have pointer events and touch events. Use then mouse events.
661+
fromEvent(element, 'mousedown').pipe(takeUntil(this._destroy))
662+
.subscribe((res) => this.onPointerDown(res));
650663
}
651-
} else if (this.touchEventsEnabled) {
652-
fromEvent(targetElement, 'touchstart').pipe(takeUntil(this._destroy))
653-
.subscribe((res) => this.onPointerDown(res));
664+
});
654665

666+
// We should bind to document events only once when there are no pointer events.
667+
if (!this.pointerEventsEnabled && this.touchEventsEnabled) {
655668
fromEvent(document.defaultView, 'touchmove').pipe(
656669
throttle(() => interval(0, animationFrameScheduler)),
657670
takeUntil(this._destroy)
658671
).subscribe((res) => this.onPointerMove(res));
659672

660673
fromEvent(document.defaultView, 'touchend').pipe(takeUntil(this._destroy))
661674
.subscribe((res) => this.onPointerUp(res));
662-
} else {
663-
// We don't have pointer events and touch events. Use then mouse events.
664-
fromEvent(targetElement, 'mousedown').pipe(takeUntil(this._destroy))
665-
.subscribe((res) => this.onPointerDown(res));
666-
675+
} else if (!this.pointerEventsEnabled) {
667676
fromEvent(document.defaultView, 'mousemove').pipe(
668677
throttle(() => interval(0, animationFrameScheduler)),
669678
takeUntil(this._destroy)
@@ -727,7 +736,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
727736
this._pointerDownId = event.pointerId;
728737

729738
// Set pointer capture so we detect pointermove even if mouse is out of bounds until dragGhost is created.
730-
const targetElement = this.dragHandle ? this.dragHandle.element.nativeElement : this.element.nativeElement;
739+
const handleFound = this.dragHandles.find(handle => handle.element.nativeElement === event.target);
740+
const targetElement = handleFound ? handleFound.element.nativeElement : this.element.nativeElement;
731741
if (this.pointerEventsEnabled) {
732742
targetElement.setPointerCapture(this._pointerDownId);
733743
} else {
@@ -749,26 +759,24 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
749759
this._startY = event.touches[0].pageY;
750760
}
751761

752-
let ghostOffsetX;
753762
if (this.ghostOffsetX !== undefined) {
754-
ghostOffsetX = this.ghostOffsetX;
763+
this._ghostOffsetX = this.ghostOffsetX;
755764
} else {
756765
const marginLeft = parseInt(document.defaultView.getComputedStyle(this.element.nativeElement)['margin-left'], 10);
757-
ghostOffsetX = this.element.nativeElement.getBoundingClientRect().left + this.getWindowScrollLeft() - marginLeft -
766+
this._ghostOffsetX = this.element.nativeElement.getBoundingClientRect().left + this.getWindowScrollLeft() - marginLeft -
758767
this._startX;
759768
}
760769

761-
let ghostOffsetY;
762770
if (this.ghostOffsetY !== undefined) {
763-
ghostOffsetY = this.ghostOffsetY;
771+
this._ghostOffsetY = this.ghostOffsetY;
764772
} else {
765773
const marginTop = parseInt(document.defaultView.getComputedStyle(this.element.nativeElement)['margin-top'], 10);
766-
ghostOffsetY = this.element.nativeElement.getBoundingClientRect().top + this.getWindowScrollTop() - marginTop -
774+
this._ghostOffsetY = this.element.nativeElement.getBoundingClientRect().top + this.getWindowScrollTop() - marginTop -
767775
this._startY;
768776
}
769777

770-
this._ghostStartX = this._startX + ghostOffsetX;
771-
this._ghostStartY = this._startY + ghostOffsetY;
778+
this._ghostStartX = this._startX + this._ghostOffsetX;
779+
this._ghostStartY = this._startY + this._ghostOffsetY;
772780
this._lastX = this._startX;
773781
this._lastY = this._startY;
774782
}
@@ -822,11 +830,13 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
822830
}
823831

824832
if (this.renderGhost) {
825-
this.ghostLeft = this._ghostStartX + totalMovedX - this._dragGhostHostX;
826-
this.ghostTop = this._ghostStartY + totalMovedY - this._dragGhostHostY;
833+
this.ghostLeft = this._ghostStartX + totalMovedX;
834+
this.ghostTop = this._ghostStartY + totalMovedY;
827835
} else {
828-
const translateX = this.getTransformX(this.element.nativeElement) + (pageX - this._lastX);
829-
const translateY = this.getTransformY(this.element.nativeElement) + (pageY - this._lastY);
836+
const lastMovedX = pageX - this._lastX;
837+
const lastMovedY = pageY - this._lastY;
838+
const translateX = this.getTransformX(this.element.nativeElement) + lastMovedX;
839+
const translateY = this.getTransformY(this.element.nativeElement) + lastMovedY;
830840
this.setTransformXY(translateX, translateY);
831841
}
832842

@@ -943,8 +953,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
943953

944954
this.dragGhost.style.transitionDuration = '0.0s';
945955
this.dragGhost.style.position = 'absolute';
946-
this.dragGhost.style.left = (this._ghostStartX + totalMovedX) - this._dragGhostHostX + 'px';
947-
this.dragGhost.style.top = (this._ghostStartY + totalMovedY) - this._dragGhostHostY + 'px';
956+
this.ghostLeft = this._ghostStartX + totalMovedX;
957+
this.ghostTop = this._ghostStartY + totalMovedY;
948958

949959
if (this.ghostImageClass) {
950960
this.renderer.addClass(this.dragGhost, this.ghostImageClass);
@@ -1007,11 +1017,9 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy {
10071017
originalEvent: originalEvent
10081018
};
10091019

1010-
this.zone.run(() => {
1011-
this.dragMove.emit({
1012-
originalEvent: originalEvent,
1013-
owner: this
1014-
});
1020+
this.dragMove.emit({
1021+
originalEvent: originalEvent,
1022+
owner: this
10151023
});
10161024

10171025
const elementsFromPoint = this.getElementsAtPoint(pageX, pageY);

projects/igniteui-angular/src/lib/grids/grid.common.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,24 +447,21 @@ export class IgxColumnMovingDragDirective extends IgxDragDirective implements On
447447
icon.classList.add('material-icons');
448448
this.cms.icon = icon;
449449

450-
const hostElemLeft = this.dragGhostHost ? this.dragGhostHost.getBoundingClientRect().left : 0;
451-
const hostElemTop = this.dragGhostHost ? this.dragGhostHost.getBoundingClientRect().top : 0;
452-
453450
if (!this.column.columnGroup) {
454451
this.renderer.addClass(icon, this.dragGhostImgIconClass);
455452

456453
this.dragGhost.insertBefore(icon, this.dragGhost.firstElementChild);
457454

458-
this.ghostLeft = this._ghostStartX = pageX - ((this.dragGhost.getBoundingClientRect().width / 3) * 2) - hostElemLeft;
459-
this.ghostTop = this._ghostStartY = pageY - ((this.dragGhost.getBoundingClientRect().height / 3) * 2) - hostElemTop;
455+
this.ghostLeft = this._ghostStartX = pageX - ((this.dragGhost.getBoundingClientRect().width / 3) * 2);
456+
this.ghostTop = this._ghostStartY = pageY - ((this.dragGhost.getBoundingClientRect().height / 3) * 2);
460457
} else {
461458
this.dragGhost.insertBefore(icon, this.dragGhost.childNodes[0]);
462459

463460
this.renderer.addClass(icon, this.dragGhostImgIconGroupClass);
464461
this.dragGhost.children[0].style.paddingLeft = '0px';
465462

466-
this.ghostLeft = this._ghostStartX = pageX - ((this.dragGhost.getBoundingClientRect().width / 3) * 2) - hostElemLeft;
467-
this.ghostTop = this._ghostStartY = pageY - ((this.dragGhost.getBoundingClientRect().height / 3) * 2) - hostElemTop;
463+
this.ghostLeft = this._ghostStartX = pageX - ((this.dragGhost.getBoundingClientRect().width / 3) * 2);
464+
this.ghostTop = this._ghostStartY = pageY - ((this.dragGhost.getBoundingClientRect().height / 3) * 2);
468465
}
469466
}
470467

0 commit comments

Comments
 (0)