Skip to content

Commit 76852bc

Browse files
committed
feat(dragOverClass): add a class when an element is dragged over it
1 parent ee1d06c commit 76852bc

File tree

3 files changed

+99
-61
lines changed

3 files changed

+99
-61
lines changed

demo/demo.component.ts

+32-37
Original file line numberDiff line numberDiff line change
@@ -10,56 +10,51 @@ import { Component } from '@angular/core';
1010
I snap to a 100 x 100 grid
1111
</div>
1212
<div
13-
[class.dropOverActive]="dropOverActive"
1413
mwlDroppable
15-
(dragEnter)="dropOverActive = true"
16-
(dragLeave)="dropOverActive = false"
17-
(drop)="onDrop($event)">
14+
(drop)="onDrop($event)"
15+
dragOverClass="dropOverActive">
1816
<span [hidden]="droppedData">Drop here</span>
1917
<span [hidden]="!droppedData">Item dropped here with data: "{{ droppedData }}"!</span>
2018
</div>
2119
`,
2220
styles: [
2321
`
24-
[mwlDraggable] {
25-
background-color: red;
26-
width: 200px;
27-
height: 200px;
28-
position: relative;
29-
z-index: 1;
30-
float: left;
31-
margin-right: 10px;
32-
}
33-
[mwlDroppable] {
34-
background-color: green;
35-
width: 400px;
36-
height: 400px;
37-
position: relative;
38-
top: 50px;
39-
left: 100px;
40-
}
41-
[mwlDraggable],
42-
[mwlDroppable] {
43-
color: white;
44-
text-align: center;
45-
display: flex;
46-
align-items: center;
47-
justify-content: center;
48-
}
49-
.dropOverActive {
50-
border: dashed 1px black;
51-
background-color: lightgreen;
52-
}
53-
`
22+
[mwlDraggable] {
23+
background-color: red;
24+
width: 200px;
25+
height: 200px;
26+
position: relative;
27+
z-index: 1;
28+
float: left;
29+
margin-right: 10px;
30+
}
31+
[mwlDroppable] {
32+
background-color: green;
33+
width: 400px;
34+
height: 400px;
35+
position: relative;
36+
top: 50px;
37+
left: 100px;
38+
}
39+
[mwlDraggable],
40+
[mwlDroppable] {
41+
color: white;
42+
text-align: center;
43+
display: flex;
44+
align-items: center;
45+
justify-content: center;
46+
}
47+
.dropOverActive {
48+
border: dashed 1px black;
49+
background-color: lightgreen;
50+
}
51+
`
5452
]
5553
})
5654
export class DemoComponent {
57-
dropOverActive: boolean = false;
58-
5955
droppedData: string = '';
6056

6157
onDrop({ dropData }: { dropData: any }): void {
62-
this.dropOverActive = false;
6358
this.droppedData = dropData;
6459
setTimeout(() => {
6560
this.droppedData = '';

src/droppable.directive.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
OnDestroy,
66
Output,
77
EventEmitter,
8-
NgZone
8+
NgZone,
9+
Input,
10+
Renderer2
911
} from '@angular/core';
1012
import { Subject, Subscription } from 'rxjs';
1113
import {
@@ -38,6 +40,11 @@ export interface DropData {
3840
selector: '[mwlDroppable]'
3941
})
4042
export class DroppableDirective implements OnInit, OnDestroy {
43+
/**
44+
* Added to the element when an element is dragged over it
45+
*/
46+
@Input() dragOverClass: string;
47+
4148
/**
4249
* Called when a draggable element starts overlapping the element
4350
*/
@@ -63,7 +70,8 @@ export class DroppableDirective implements OnInit, OnDestroy {
6370
constructor(
6471
private element: ElementRef,
6572
private draggableHelper: DraggableHelper,
66-
private zone: NgZone
73+
private zone: NgZone,
74+
private renderer: Renderer2
6775
) {}
6876

6977
ngOnInit(): void {
@@ -97,6 +105,10 @@ export class DroppableDirective implements OnInit, OnDestroy {
97105
.pipe(filter(overlapsNow => overlapsNow))
98106
.subscribe(() => {
99107
dragOverActive = true;
108+
this.renderer.addClass(
109+
this.element.nativeElement,
110+
this.dragOverClass
111+
);
100112
this.zone.run(() => {
101113
this.dragEnter.next({
102114
dropData: currentDragDropData
@@ -119,6 +131,10 @@ export class DroppableDirective implements OnInit, OnDestroy {
119131
)
120132
.subscribe(() => {
121133
dragOverActive = false;
134+
this.renderer.removeClass(
135+
this.element.nativeElement,
136+
this.dragOverClass
137+
);
122138
this.zone.run(() => {
123139
this.dragLeave.next({
124140
dropData: currentDragDropData
@@ -129,6 +145,10 @@ export class DroppableDirective implements OnInit, OnDestroy {
129145
drag.pipe(mergeMap(() => overlaps)).subscribe({
130146
complete: () => {
131147
if (dragOverActive) {
148+
this.renderer.removeClass(
149+
this.element.nativeElement,
150+
this.dragOverClass
151+
);
132152
this.zone.run(() => {
133153
this.drop.next({
134154
dropData: currentDragDropData

test/droppable.directive.spec.ts

+45-22
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,55 @@
1-
import { Component, ViewChild } from '@angular/core';
1+
import { Component, ElementRef, ViewChild } from '@angular/core';
22
import { TestBed, ComponentFixture } from '@angular/core/testing';
33
import { expect } from 'chai';
44
import * as sinon from 'sinon';
55
import { triggerDomEvent } from './util';
66
import { DragAndDropModule } from '../src/index';
77
import { DraggableDirective } from '../src/draggable.directive';
8+
import { DroppableDirective } from '../src/droppable.directive';
89

910
describe('droppable directive', () => {
1011
@Component({
1112
template: `
1213
<div mwlDraggable [dropData]="dropData">Drag me!</div>
1314
<div
15+
#droppableElement
1416
mwlDroppable
1517
(dragEnter)="dragEvent('enter', $event)"
1618
(dragOver)="dragEvent('over', $event)"
1719
(dragLeave)="dragEvent('leave', $event)"
18-
(drop)="drop($event)">
20+
(drop)="drop($event)"
21+
[dragOverClass]="dragOverClass">
1922
Drop here
2023
</div>
2124
`,
2225
styles: [
2326
`
24-
[mwlDraggable] {
25-
position: relative;
26-
width: 50px;
27-
height: 50px;
28-
z-index: 1;
29-
margin-top: -200px;
30-
}
31-
[mwlDroppable] {
32-
position: relative;
33-
top: 100px;
34-
left: 0;
35-
width: 200px;
36-
height: 200px;
37-
}
38-
`
27+
[mwlDraggable] {
28+
position: relative;
29+
width: 50px;
30+
height: 50px;
31+
z-index: 1;
32+
margin-top: -200px;
33+
}
34+
[mwlDroppable] {
35+
position: relative;
36+
top: 100px;
37+
left: 0;
38+
width: 200px;
39+
height: 200px;
40+
}
41+
`
3942
]
4043
})
4144
class TestComponent {
42-
@ViewChild(DraggableDirective) public draggable: DraggableDirective;
43-
public dragEvent: sinon.SinonSpy = sinon.spy();
44-
public drop: sinon.SinonSpy = sinon.spy();
45-
46-
public dropData: {
45+
@ViewChild(DraggableDirective) draggable: DraggableDirective;
46+
@ViewChild('droppableElement') droppableElement: ElementRef;
47+
dragEvent: sinon.SinonSpy = sinon.spy();
48+
drop: sinon.SinonSpy = sinon.spy();
49+
dropData: {
4750
foo: 'bar';
4851
};
52+
dragOverClass: string;
4953
}
5054

5155
beforeEach(() => {
@@ -152,4 +156,23 @@ describe('droppable directive', () => {
152156
{ dropData: { foo: 'bar' } }
153157
]);
154158
});
159+
160+
it('should add a class to the droppable element when an element is dragged over it', () => {
161+
fixture.componentInstance.dragOverClass = 'drag-over';
162+
fixture.detectChanges();
163+
const draggableElement: HTMLElement =
164+
fixture.componentInstance.draggable.element.nativeElement;
165+
const droppableElement: HTMLElement =
166+
fixture.componentInstance.droppableElement.nativeElement;
167+
expect(droppableElement.classList.contains('drag-over')).to.be.false;
168+
triggerDomEvent('mousedown', draggableElement, { clientX: 5, clientY: 10 });
169+
expect(droppableElement.classList.contains('drag-over')).to.be.false;
170+
triggerDomEvent('mousemove', draggableElement, {
171+
clientX: 5,
172+
clientY: 100
173+
});
174+
expect(droppableElement.classList.contains('drag-over')).to.be.true;
175+
triggerDomEvent('mouseup', draggableElement, { clientX: 5, clientY: 120 });
176+
expect(droppableElement.classList.contains('drag-over')).to.be.false;
177+
});
155178
});

0 commit comments

Comments
 (0)