Skip to content

Commit

Permalink
feat: add validateDrop to mwlDroppable directive (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
candersony authored Dec 28, 2020
1 parent 44635b1 commit 8850ddf
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface CurrentDragData {
clientX: number;
clientY: number;
dropData: any;
target: EventTarget;
}

@Injectable({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
clientY: pointerMoveEvent.clientY,
scrollLeft: scroll.left,
scrollTop: scroll.top,
target: pointerMoveEvent.event.target,
};
}),
map((moveData) => {
Expand Down Expand Up @@ -518,7 +519,16 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
map(([previous, next]) => next)
)
.subscribe(
({ x, y, currentDrag$, clientX, clientY, transformX, transformY }) => {
({
x,
y,
currentDrag$,
clientX,
clientY,
transformX,
transformY,
target,
}) => {
this.zone.run(() => {
this.dragging.next({ x, y });
});
Expand All @@ -538,6 +548,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
clientX,
clientY,
dropData: this.dropData,
target,
});
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as sinon from 'sinon';
import { triggerDomEvent } from '../test-utils';
import { DragAndDropModule } from 'angular-draggable-droppable';
import { DraggableDirective } from './draggable.directive';
import { DroppableDirective } from './droppable.directive';
import { DroppableDirective, ValidateDrop } from './droppable.directive';
import { DraggableScrollContainerDirective } from './draggable-scroll-container.directive';
import { By } from '@angular/platform-browser';

Expand All @@ -29,6 +29,7 @@ describe('droppable directive', () => {
(drop)="drop($event)"
[dragOverClass]="dragOverClass"
[dragActiveClass]="dragActiveClass"
[validateDrop]="validateDrop"
>
Drop here
</div>
Expand Down Expand Up @@ -69,6 +70,7 @@ describe('droppable directive', () => {
};
dragOverClass: string;
dragActiveClass: string;
validateDrop: ValidateDrop;
}

@Component({
Expand Down Expand Up @@ -411,4 +413,56 @@ describe('droppable directive', () => {
});
expect(scrollFixture.componentInstance.drop).not.to.have.been.called;
});

it('should fire drop events when validateDrop returns true', () => {
const draggableElement =
fixture.componentInstance.draggableElement.nativeElement;
const droppableElement =
fixture.componentInstance.droppableElement.nativeElement;
const elementInsideDroppableArea = document.createElement('div');
droppableElement.appendChild(elementInsideDroppableArea);
fixture.componentInstance.validateDrop = () => true;
fixture.detectChanges();
triggerDomEvent('mousedown', draggableElement, {
clientX: 5,
clientY: 10,
button: 0,
});
triggerDomEvent('mousemove', elementInsideDroppableArea, {
clientX: 5,
clientY: 120,
});
triggerDomEvent('mouseup', draggableElement, {
clientX: 5,
clientY: 120,
button: 0,
});
expect(fixture.componentInstance.drop).to.have.been.called;
});

it('should not fire drop events when validateDrop returns false', () => {
const draggableElement =
fixture.componentInstance.draggableElement.nativeElement;
const droppableElement =
fixture.componentInstance.droppableElement.nativeElement;
const elementOutsideDroppableArea = document.createElement('div');
document.body.appendChild(elementOutsideDroppableArea);
fixture.componentInstance.validateDrop = () => false;
fixture.detectChanges();
triggerDomEvent('mousedown', draggableElement, {
clientX: 5,
clientY: 10,
button: 0,
});
triggerDomEvent('mousemove', elementOutsideDroppableArea, {
clientX: 5,
clientY: 120,
});
triggerDomEvent('mouseup', draggableElement, {
clientX: 5,
clientY: 120,
button: 0,
});
expect(fixture.componentInstance.drop).not.to.have.been.called;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ export interface DropEvent<T = any> {
dropData: T;
}

export interface ValidateDropParams {
/**
* ClientX value of the mouse location where the drop occurred
*/
clientX: number;
/**
* ClientY value of the mouse location where the drop occurred
*/
clientY: number;
/**
* The target of the event where the drop occurred
*/
target: EventTarget;
}

export type ValidateDrop = (params: ValidateDropParams) => boolean;

@Directive({
selector: '[mwlDroppable]',
})
Expand All @@ -47,6 +64,11 @@ export class DroppableDirective implements OnInit, OnDestroy {
*/
@Input() dragActiveClass: string;

/**
* Allow custom behaviour to control when the element is dropped
*/
@Input() validateDrop: ValidateDrop;

/**
* Called when a draggable element starts overlapping the element
*/
Expand Down Expand Up @@ -101,7 +123,7 @@ export class DroppableDirective implements OnInit, OnDestroy {

let currentDragDropData: any;
const overlaps$ = drag$.pipe(
map(({ clientX, clientY, dropData }) => {
map(({ clientX, clientY, dropData, target }) => {
currentDragDropData = dropData;
if (droppableElement.updateCache) {
droppableElement.rect = this.element.nativeElement.getBoundingClientRect();
Expand All @@ -115,17 +137,23 @@ export class DroppableDirective implements OnInit, OnDestroy {
clientY,
droppableElement.rect as ClientRect
);

const isDropAllowed =
!this.validateDrop ||
this.validateDrop({ clientX, clientY, target });

if (droppableElement.scrollContainerRect) {
return (
isWithinElement &&
isDropAllowed &&
isCoordinateWithinRectangle(
clientX,
clientY,
droppableElement.scrollContainerRect as ClientRect
)
);
} else {
return isWithinElement;
return isWithinElement && isDropAllowed;
}
})
);
Expand Down
6 changes: 5 additions & 1 deletion projects/angular-draggable-droppable/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
*/

export * from './lib/drag-and-drop.module';
export { DropEvent } from './lib/droppable.directive';
export {
DropEvent,
ValidateDrop,
ValidateDropParams,
} from './lib/droppable.directive';
export {
DragPointerDownEvent,
DragStartEvent,
Expand Down
28 changes: 28 additions & 0 deletions src/demo/demo.component.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:host {
display: flex;
}

[mwlDraggable] {
background-color: red;
width: 200px;
Expand All @@ -19,6 +23,10 @@
left: 100px;
}

.validate-drop {
left: 150px;
}

[mwlDraggable],
[mwlDroppable] {
color: white;
Expand All @@ -36,3 +44,23 @@
.drag-active {
z-index: 3;
}

.floating-toolbar {
position: absolute;
top: 140px;
z-index: 2;
width: 250px;
height: 75px;
background: yellow;
display: flex;
align-items: center;
justify-content: center;
}

.floating-toolbar-1 {
left: 600px;
}

.floating-toolbar-2 {
left: 1050px;
}
19 changes: 19 additions & 0 deletions src/demo/demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,22 @@
>Item dropped here with data: "{{ droppedData }}"!</span
>
</div>
<div
mwlDroppable
(drop)="onDrop2($event)"
dragOverClass="drop-over-active"
[validateDrop]="validateDrop"
class="validate-drop"
>
<span [hidden]="droppedData2">Drop here</span>
<span [hidden]="!droppedData2"
>Item dropped here with data: "{{ droppedData2 }}"!</span
>
</div>

<div class="floating-toolbar floating-toolbar-1">
Floating toolbar, drop here too!
</div>
<div class="floating-toolbar floating-toolbar-2">
Floating toolbar, can't drop here!
</div>
20 changes: 19 additions & 1 deletion src/demo/demo.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Component } from '@angular/core';
import { Component, ElementRef, ViewChild } from '@angular/core';
import { DropEvent } from 'angular-draggable-droppable';
import {
DroppableDirective,
ValidateDrop,
} from 'projects/angular-draggable-droppable/src/lib/droppable.directive';

@Component({
selector: 'mwl-demo-app',
Expand All @@ -8,11 +12,25 @@ import { DropEvent } from 'angular-draggable-droppable';
})
export class DemoComponent {
droppedData: string = '';
droppedData2: string = '';

@ViewChild(DroppableDirective, { read: ElementRef })
droppableElement: ElementRef;

onDrop({ dropData }: DropEvent<string>): void {
this.droppedData = dropData;
setTimeout(() => {
this.droppedData = '';
}, 2000);
}

onDrop2({ dropData }: DropEvent<string>): void {
this.droppedData2 = dropData;
setTimeout(() => {
this.droppedData2 = '';
}, 2000);
}

validateDrop: ValidateDrop = ({ target }) =>
this.droppableElement.nativeElement.contains(target as Node);
}

0 comments on commit 8850ddf

Please sign in to comment.