UNPKG

36 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { Injectable, Directive, EventEmitter, Optional, Inject, Input, Output, NgModule } from '@angular/core';
3import { Subject, Observable, ReplaySubject, merge, combineLatest, fromEvent } from 'rxjs';
4import { filter, mergeMap, startWith, map, share, takeUntil, take, takeLast, count, pairwise, distinctUntilChanged } from 'rxjs/operators';
5import { DOCUMENT } from '@angular/common';
6import autoScroll from '@mattlewis92/dom-autoscroller';
7
8function addClass(renderer, element, classToAdd) {
9 if (classToAdd) {
10 classToAdd
11 .split(' ')
12 .forEach((className) => renderer.addClass(element.nativeElement, className));
13 }
14}
15function removeClass(renderer, element, classToRemove) {
16 if (classToRemove) {
17 classToRemove
18 .split(' ')
19 .forEach((className) => renderer.removeClass(element.nativeElement, className));
20 }
21}
22
23class DraggableHelper {
24 constructor() {
25 this.currentDrag = new Subject();
26 }
27}
28DraggableHelper.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableHelper, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
29DraggableHelper.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableHelper, providedIn: 'root' });
30i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableHelper, decorators: [{
31 type: Injectable,
32 args: [{
33 providedIn: 'root',
34 }]
35 }] });
36
37/**
38 * If the window isn't scrollable, then place this on the scrollable container that draggable elements are inside. e.g.
39 * ```html
40 <div style="overflow: scroll" mwlDraggableScrollContainer>
41 <div mwlDraggable>Drag me!</div>
42 </div>
43 ```
44 */
45class DraggableScrollContainerDirective {
46 /**
47 * @hidden
48 */
49 constructor(elementRef) {
50 this.elementRef = elementRef;
51 }
52}
53DraggableScrollContainerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableScrollContainerDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
54DraggableScrollContainerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.3", type: DraggableScrollContainerDirective, selector: "[mwlDraggableScrollContainer]", ngImport: i0 });
55i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableScrollContainerDirective, decorators: [{
56 type: Directive,
57 args: [{
58 selector: '[mwlDraggableScrollContainer]',
59 }]
60 }], ctorParameters: function () { return [{ type: i0.ElementRef }]; } });
61
62class DraggableDirective {
63 /**
64 * @hidden
65 */
66 constructor(element, renderer, draggableHelper, zone, vcr, scrollContainer, document) {
67 this.element = element;
68 this.renderer = renderer;
69 this.draggableHelper = draggableHelper;
70 this.zone = zone;
71 this.vcr = vcr;
72 this.scrollContainer = scrollContainer;
73 this.document = document;
74 /**
75 * The axis along which the element is draggable
76 */
77 this.dragAxis = { x: true, y: true };
78 /**
79 * Snap all drags to an x / y grid
80 */
81 this.dragSnapGrid = {};
82 /**
83 * Show a ghost element that shows the drag when dragging
84 */
85 this.ghostDragEnabled = true;
86 /**
87 * Show the original element when ghostDragEnabled is true
88 */
89 this.showOriginalElementWhileDragging = false;
90 /**
91 * The cursor to use when hovering over a draggable element
92 */
93 this.dragCursor = '';
94 /*
95 * Options used to control the behaviour of auto scrolling: https://www.npmjs.com/package/dom-autoscroller
96 */
97 this.autoScroll = {
98 margin: 20,
99 };
100 /**
101 * Called when the element can be dragged along one axis and has the mouse or pointer device pressed on it
102 */
103 this.dragPointerDown = new EventEmitter();
104 /**
105 * Called when the element has started to be dragged.
106 * Only called after at least one mouse or touch move event.
107 * If you call $event.cancelDrag$.emit() it will cancel the current drag
108 */
109 this.dragStart = new EventEmitter();
110 /**
111 * Called after the ghost element has been created
112 */
113 this.ghostElementCreated = new EventEmitter();
114 /**
115 * Called when the element is being dragged
116 */
117 this.dragging = new EventEmitter();
118 /**
119 * Called after the element is dragged
120 */
121 this.dragEnd = new EventEmitter();
122 /**
123 * @hidden
124 */
125 this.pointerDown$ = new Subject();
126 /**
127 * @hidden
128 */
129 this.pointerMove$ = new Subject();
130 /**
131 * @hidden
132 */
133 this.pointerUp$ = new Subject();
134 this.eventListenerSubscriptions = {};
135 this.destroy$ = new Subject();
136 this.timeLongPress = { timerBegin: 0, timerEnd: 0 };
137 }
138 ngOnInit() {
139 this.checkEventListeners();
140 const pointerDragged$ = this.pointerDown$.pipe(filter(() => this.canDrag()), mergeMap((pointerDownEvent) => {
141 // fix for https://github.com/mattlewis92/angular-draggable-droppable/issues/61
142 // stop mouse events propagating up the chain
143 if (pointerDownEvent.event.stopPropagation && !this.scrollContainer) {
144 pointerDownEvent.event.stopPropagation();
145 }
146 // hack to prevent text getting selected in safari while dragging
147 const globalDragStyle = this.renderer.createElement('style');
148 this.renderer.setAttribute(globalDragStyle, 'type', 'text/css');
149 this.renderer.appendChild(globalDragStyle, this.renderer.createText(`
150 body * {
151 -moz-user-select: none;
152 -ms-user-select: none;
153 -webkit-user-select: none;
154 user-select: none;
155 }
156 `));
157 requestAnimationFrame(() => {
158 this.document.head.appendChild(globalDragStyle);
159 });
160 const startScrollPosition = this.getScrollPosition();
161 const scrollContainerScroll$ = new Observable((observer) => {
162 const scrollContainer = this.scrollContainer
163 ? this.scrollContainer.elementRef.nativeElement
164 : 'window';
165 return this.renderer.listen(scrollContainer, 'scroll', (e) => observer.next(e));
166 }).pipe(startWith(startScrollPosition), map(() => this.getScrollPosition()));
167 const currentDrag$ = new Subject();
168 const cancelDrag$ = new ReplaySubject();
169 if (this.dragPointerDown.observers.length > 0) {
170 this.zone.run(() => {
171 this.dragPointerDown.next({ x: 0, y: 0 });
172 });
173 }
174 const dragComplete$ = merge(this.pointerUp$, this.pointerDown$, cancelDrag$, this.destroy$).pipe(share());
175 const pointerMove = combineLatest([
176 this.pointerMove$,
177 scrollContainerScroll$,
178 ]).pipe(map(([pointerMoveEvent, scroll]) => {
179 return {
180 currentDrag$,
181 transformX: pointerMoveEvent.clientX - pointerDownEvent.clientX,
182 transformY: pointerMoveEvent.clientY - pointerDownEvent.clientY,
183 clientX: pointerMoveEvent.clientX,
184 clientY: pointerMoveEvent.clientY,
185 scrollLeft: scroll.left,
186 scrollTop: scroll.top,
187 target: pointerMoveEvent.event.target,
188 };
189 }), map((moveData) => {
190 if (this.dragSnapGrid.x) {
191 moveData.transformX =
192 Math.round(moveData.transformX / this.dragSnapGrid.x) *
193 this.dragSnapGrid.x;
194 }
195 if (this.dragSnapGrid.y) {
196 moveData.transformY =
197 Math.round(moveData.transformY / this.dragSnapGrid.y) *
198 this.dragSnapGrid.y;
199 }
200 return moveData;
201 }), map((moveData) => {
202 if (!this.dragAxis.x) {
203 moveData.transformX = 0;
204 }
205 if (!this.dragAxis.y) {
206 moveData.transformY = 0;
207 }
208 return moveData;
209 }), map((moveData) => {
210 const scrollX = moveData.scrollLeft - startScrollPosition.left;
211 const scrollY = moveData.scrollTop - startScrollPosition.top;
212 return {
213 ...moveData,
214 x: moveData.transformX + scrollX,
215 y: moveData.transformY + scrollY,
216 };
217 }), filter(({ x, y, transformX, transformY }) => !this.validateDrag ||
218 this.validateDrag({
219 x,
220 y,
221 transform: { x: transformX, y: transformY },
222 })), takeUntil(dragComplete$), share());
223 const dragStarted$ = pointerMove.pipe(take(1), share());
224 const dragEnded$ = pointerMove.pipe(takeLast(1), share());
225 dragStarted$.subscribe(({ clientX, clientY, x, y }) => {
226 if (this.dragStart.observers.length > 0) {
227 this.zone.run(() => {
228 this.dragStart.next({ cancelDrag$ });
229 });
230 }
231 this.scroller = autoScroll([
232 this.scrollContainer
233 ? this.scrollContainer.elementRef.nativeElement
234 : this.document.defaultView,
235 ], {
236 ...this.autoScroll,
237 autoScroll() {
238 return true;
239 },
240 });
241 addClass(this.renderer, this.element, this.dragActiveClass);
242 if (this.ghostDragEnabled) {
243 const rect = this.element.nativeElement.getBoundingClientRect();
244 const clone = this.element.nativeElement.cloneNode(true);
245 if (!this.showOriginalElementWhileDragging) {
246 this.renderer.setStyle(this.element.nativeElement, 'visibility', 'hidden');
247 }
248 if (this.ghostElementAppendTo) {
249 this.ghostElementAppendTo.appendChild(clone);
250 }
251 else {
252 this.element.nativeElement.parentNode.insertBefore(clone, this.element.nativeElement.nextSibling);
253 }
254 this.ghostElement = clone;
255 this.document.body.style.cursor = this.dragCursor;
256 this.setElementStyles(clone, {
257 position: 'fixed',
258 top: `${rect.top}px`,
259 left: `${rect.left}px`,
260 width: `${rect.width}px`,
261 height: `${rect.height}px`,
262 cursor: this.dragCursor,
263 margin: '0',
264 willChange: 'transform',
265 pointerEvents: 'none',
266 });
267 if (this.ghostElementTemplate) {
268 const viewRef = this.vcr.createEmbeddedView(this.ghostElementTemplate);
269 clone.innerHTML = '';
270 viewRef.rootNodes
271 .filter((node) => node instanceof Node)
272 .forEach((node) => {
273 clone.appendChild(node);
274 });
275 dragEnded$.subscribe(() => {
276 this.vcr.remove(this.vcr.indexOf(viewRef));
277 });
278 }
279 if (this.ghostElementCreated.observers.length > 0) {
280 this.zone.run(() => {
281 this.ghostElementCreated.emit({
282 clientX: clientX - x,
283 clientY: clientY - y,
284 element: clone,
285 });
286 });
287 }
288 dragEnded$.subscribe(() => {
289 clone.parentElement.removeChild(clone);
290 this.ghostElement = null;
291 this.renderer.setStyle(this.element.nativeElement, 'visibility', '');
292 });
293 }
294 this.draggableHelper.currentDrag.next(currentDrag$);
295 });
296 dragEnded$
297 .pipe(mergeMap((dragEndData) => {
298 const dragEndData$ = cancelDrag$.pipe(count(), take(1), map((calledCount) => ({
299 ...dragEndData,
300 dragCancelled: calledCount > 0,
301 })));
302 cancelDrag$.complete();
303 return dragEndData$;
304 }))
305 .subscribe(({ x, y, dragCancelled }) => {
306 this.scroller.destroy();
307 if (this.dragEnd.observers.length > 0) {
308 this.zone.run(() => {
309 this.dragEnd.next({ x, y, dragCancelled });
310 });
311 }
312 removeClass(this.renderer, this.element, this.dragActiveClass);
313 currentDrag$.complete();
314 });
315 merge(dragComplete$, dragEnded$)
316 .pipe(take(1))
317 .subscribe(() => {
318 requestAnimationFrame(() => {
319 this.document.head.removeChild(globalDragStyle);
320 });
321 });
322 return pointerMove;
323 }), share());
324 merge(pointerDragged$.pipe(take(1), map((value) => [, value])), pointerDragged$.pipe(pairwise()))
325 .pipe(filter(([previous, next]) => {
326 if (!previous) {
327 return true;
328 }
329 return previous.x !== next.x || previous.y !== next.y;
330 }), map(([previous, next]) => next))
331 .subscribe(({ x, y, currentDrag$, clientX, clientY, transformX, transformY, target, }) => {
332 if (this.dragging.observers.length > 0) {
333 this.zone.run(() => {
334 this.dragging.next({ x, y });
335 });
336 }
337 requestAnimationFrame(() => {
338 if (this.ghostElement) {
339 const transform = `translate3d(${transformX}px, ${transformY}px, 0px)`;
340 this.setElementStyles(this.ghostElement, {
341 transform,
342 '-webkit-transform': transform,
343 '-ms-transform': transform,
344 '-moz-transform': transform,
345 '-o-transform': transform,
346 });
347 }
348 });
349 currentDrag$.next({
350 clientX,
351 clientY,
352 dropData: this.dropData,
353 target,
354 });
355 });
356 }
357 ngOnChanges(changes) {
358 if (changes.dragAxis) {
359 this.checkEventListeners();
360 }
361 }
362 ngOnDestroy() {
363 this.unsubscribeEventListeners();
364 this.pointerDown$.complete();
365 this.pointerMove$.complete();
366 this.pointerUp$.complete();
367 this.destroy$.next();
368 }
369 checkEventListeners() {
370 const canDrag = this.canDrag();
371 const hasEventListeners = Object.keys(this.eventListenerSubscriptions).length > 0;
372 if (canDrag && !hasEventListeners) {
373 this.zone.runOutsideAngular(() => {
374 this.eventListenerSubscriptions.mousedown = this.renderer.listen(this.element.nativeElement, 'mousedown', (event) => {
375 this.onMouseDown(event);
376 });
377 this.eventListenerSubscriptions.mouseup = this.renderer.listen('document', 'mouseup', (event) => {
378 this.onMouseUp(event);
379 });
380 this.eventListenerSubscriptions.touchstart = this.renderer.listen(this.element.nativeElement, 'touchstart', (event) => {
381 this.onTouchStart(event);
382 });
383 this.eventListenerSubscriptions.touchend = this.renderer.listen('document', 'touchend', (event) => {
384 this.onTouchEnd(event);
385 });
386 this.eventListenerSubscriptions.touchcancel = this.renderer.listen('document', 'touchcancel', (event) => {
387 this.onTouchEnd(event);
388 });
389 this.eventListenerSubscriptions.mouseenter = this.renderer.listen(this.element.nativeElement, 'mouseenter', () => {
390 this.onMouseEnter();
391 });
392 this.eventListenerSubscriptions.mouseleave = this.renderer.listen(this.element.nativeElement, 'mouseleave', () => {
393 this.onMouseLeave();
394 });
395 });
396 }
397 else if (!canDrag && hasEventListeners) {
398 this.unsubscribeEventListeners();
399 }
400 }
401 onMouseDown(event) {
402 if (event.button === 0) {
403 if (!this.eventListenerSubscriptions.mousemove) {
404 this.eventListenerSubscriptions.mousemove = this.renderer.listen('document', 'mousemove', (mouseMoveEvent) => {
405 this.pointerMove$.next({
406 event: mouseMoveEvent,
407 clientX: mouseMoveEvent.clientX,
408 clientY: mouseMoveEvent.clientY,
409 });
410 });
411 }
412 this.pointerDown$.next({
413 event,
414 clientX: event.clientX,
415 clientY: event.clientY,
416 });
417 }
418 }
419 onMouseUp(event) {
420 if (event.button === 0) {
421 if (this.eventListenerSubscriptions.mousemove) {
422 this.eventListenerSubscriptions.mousemove();
423 delete this.eventListenerSubscriptions.mousemove;
424 }
425 this.pointerUp$.next({
426 event,
427 clientX: event.clientX,
428 clientY: event.clientY,
429 });
430 }
431 }
432 onTouchStart(event) {
433 let startScrollPosition;
434 let isDragActivated;
435 let hasContainerScrollbar;
436 if (this.touchStartLongPress) {
437 this.timeLongPress.timerBegin = Date.now();
438 isDragActivated = false;
439 hasContainerScrollbar = this.hasScrollbar();
440 startScrollPosition = this.getScrollPosition();
441 }
442 if (!this.eventListenerSubscriptions.touchmove) {
443 const contextMenuListener = fromEvent(this.document, 'contextmenu').subscribe((e) => {
444 e.preventDefault();
445 });
446 const touchMoveListener = fromEvent(this.document, 'touchmove', {
447 passive: false,
448 }).subscribe((touchMoveEvent) => {
449 if (this.touchStartLongPress &&
450 !isDragActivated &&
451 hasContainerScrollbar) {
452 isDragActivated = this.shouldBeginDrag(event, touchMoveEvent, startScrollPosition);
453 }
454 if (!this.touchStartLongPress ||
455 !hasContainerScrollbar ||
456 isDragActivated) {
457 touchMoveEvent.preventDefault();
458 this.pointerMove$.next({
459 event: touchMoveEvent,
460 clientX: touchMoveEvent.targetTouches[0].clientX,
461 clientY: touchMoveEvent.targetTouches[0].clientY,
462 });
463 }
464 });
465 this.eventListenerSubscriptions.touchmove = () => {
466 contextMenuListener.unsubscribe();
467 touchMoveListener.unsubscribe();
468 };
469 }
470 this.pointerDown$.next({
471 event,
472 clientX: event.touches[0].clientX,
473 clientY: event.touches[0].clientY,
474 });
475 }
476 onTouchEnd(event) {
477 if (this.eventListenerSubscriptions.touchmove) {
478 this.eventListenerSubscriptions.touchmove();
479 delete this.eventListenerSubscriptions.touchmove;
480 if (this.touchStartLongPress) {
481 this.enableScroll();
482 }
483 }
484 this.pointerUp$.next({
485 event,
486 clientX: event.changedTouches[0].clientX,
487 clientY: event.changedTouches[0].clientY,
488 });
489 }
490 onMouseEnter() {
491 this.setCursor(this.dragCursor);
492 }
493 onMouseLeave() {
494 this.setCursor('');
495 }
496 canDrag() {
497 return this.dragAxis.x || this.dragAxis.y;
498 }
499 setCursor(value) {
500 if (!this.eventListenerSubscriptions.mousemove) {
501 this.renderer.setStyle(this.element.nativeElement, 'cursor', value);
502 }
503 }
504 unsubscribeEventListeners() {
505 Object.keys(this.eventListenerSubscriptions).forEach((type) => {
506 this.eventListenerSubscriptions[type]();
507 delete this.eventListenerSubscriptions[type];
508 });
509 }
510 setElementStyles(element, styles) {
511 Object.keys(styles).forEach((key) => {
512 this.renderer.setStyle(element, key, styles[key]);
513 });
514 }
515 getScrollElement() {
516 if (this.scrollContainer) {
517 return this.scrollContainer.elementRef.nativeElement;
518 }
519 else {
520 return this.document.body;
521 }
522 }
523 getScrollPosition() {
524 if (this.scrollContainer) {
525 return {
526 top: this.scrollContainer.elementRef.nativeElement.scrollTop,
527 left: this.scrollContainer.elementRef.nativeElement.scrollLeft,
528 };
529 }
530 else {
531 return {
532 top: window.pageYOffset || this.document.documentElement.scrollTop,
533 left: window.pageXOffset || this.document.documentElement.scrollLeft,
534 };
535 }
536 }
537 shouldBeginDrag(event, touchMoveEvent, startScrollPosition) {
538 const moveScrollPosition = this.getScrollPosition();
539 const deltaScroll = {
540 top: Math.abs(moveScrollPosition.top - startScrollPosition.top),
541 left: Math.abs(moveScrollPosition.left - startScrollPosition.left),
542 };
543 const deltaX = Math.abs(touchMoveEvent.targetTouches[0].clientX - event.touches[0].clientX) - deltaScroll.left;
544 const deltaY = Math.abs(touchMoveEvent.targetTouches[0].clientY - event.touches[0].clientY) - deltaScroll.top;
545 const deltaTotal = deltaX + deltaY;
546 const longPressConfig = this.touchStartLongPress;
547 if (deltaTotal > longPressConfig.delta ||
548 deltaScroll.top > 0 ||
549 deltaScroll.left > 0) {
550 this.timeLongPress.timerBegin = Date.now();
551 }
552 this.timeLongPress.timerEnd = Date.now();
553 const duration = this.timeLongPress.timerEnd - this.timeLongPress.timerBegin;
554 if (duration >= longPressConfig.delay) {
555 this.disableScroll();
556 return true;
557 }
558 return false;
559 }
560 enableScroll() {
561 if (this.scrollContainer) {
562 this.renderer.setStyle(this.scrollContainer.elementRef.nativeElement, 'overflow', '');
563 }
564 this.renderer.setStyle(this.document.body, 'overflow', '');
565 }
566 disableScroll() {
567 /* istanbul ignore next */
568 if (this.scrollContainer) {
569 this.renderer.setStyle(this.scrollContainer.elementRef.nativeElement, 'overflow', 'hidden');
570 }
571 this.renderer.setStyle(this.document.body, 'overflow', 'hidden');
572 }
573 hasScrollbar() {
574 const scrollContainer = this.getScrollElement();
575 const containerHasHorizontalScroll = scrollContainer.scrollWidth > scrollContainer.clientWidth;
576 const containerHasVerticalScroll = scrollContainer.scrollHeight > scrollContainer.clientHeight;
577 return containerHasHorizontalScroll || containerHasVerticalScroll;
578 }
579}
580DraggableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: DraggableHelper }, { token: i0.NgZone }, { token: i0.ViewContainerRef }, { token: DraggableScrollContainerDirective, optional: true }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
581DraggableDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.3", type: DraggableDirective, selector: "[mwlDraggable]", inputs: { dropData: "dropData", dragAxis: "dragAxis", dragSnapGrid: "dragSnapGrid", ghostDragEnabled: "ghostDragEnabled", showOriginalElementWhileDragging: "showOriginalElementWhileDragging", validateDrag: "validateDrag", dragCursor: "dragCursor", dragActiveClass: "dragActiveClass", ghostElementAppendTo: "ghostElementAppendTo", ghostElementTemplate: "ghostElementTemplate", touchStartLongPress: "touchStartLongPress", autoScroll: "autoScroll" }, outputs: { dragPointerDown: "dragPointerDown", dragStart: "dragStart", ghostElementCreated: "ghostElementCreated", dragging: "dragging", dragEnd: "dragEnd" }, usesOnChanges: true, ngImport: i0 });
582i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableDirective, decorators: [{
583 type: Directive,
584 args: [{
585 selector: '[mwlDraggable]',
586 }]
587 }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: DraggableHelper }, { type: i0.NgZone }, { type: i0.ViewContainerRef }, { type: DraggableScrollContainerDirective, decorators: [{
588 type: Optional
589 }] }, { type: undefined, decorators: [{
590 type: Inject,
591 args: [DOCUMENT]
592 }] }]; }, propDecorators: { dropData: [{
593 type: Input
594 }], dragAxis: [{
595 type: Input
596 }], dragSnapGrid: [{
597 type: Input
598 }], ghostDragEnabled: [{
599 type: Input
600 }], showOriginalElementWhileDragging: [{
601 type: Input
602 }], validateDrag: [{
603 type: Input
604 }], dragCursor: [{
605 type: Input
606 }], dragActiveClass: [{
607 type: Input
608 }], ghostElementAppendTo: [{
609 type: Input
610 }], ghostElementTemplate: [{
611 type: Input
612 }], touchStartLongPress: [{
613 type: Input
614 }], autoScroll: [{
615 type: Input
616 }], dragPointerDown: [{
617 type: Output
618 }], dragStart: [{
619 type: Output
620 }], ghostElementCreated: [{
621 type: Output
622 }], dragging: [{
623 type: Output
624 }], dragEnd: [{
625 type: Output
626 }] } });
627
628function isCoordinateWithinRectangle(clientX, clientY, rect) {
629 return (clientX >= rect.left &&
630 clientX <= rect.right &&
631 clientY >= rect.top &&
632 clientY <= rect.bottom);
633}
634class DroppableDirective {
635 constructor(element, draggableHelper, zone, renderer, scrollContainer) {
636 this.element = element;
637 this.draggableHelper = draggableHelper;
638 this.zone = zone;
639 this.renderer = renderer;
640 this.scrollContainer = scrollContainer;
641 /**
642 * Called when a draggable element starts overlapping the element
643 */
644 this.dragEnter = new EventEmitter();
645 /**
646 * Called when a draggable element stops overlapping the element
647 */
648 this.dragLeave = new EventEmitter();
649 /**
650 * Called when a draggable element is moved over the element
651 */
652 this.dragOver = new EventEmitter();
653 /**
654 * Called when a draggable element is dropped on this element
655 */
656 this.drop = new EventEmitter(); // eslint-disable-line @angular-eslint/no-output-native
657 }
658 ngOnInit() {
659 this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag$) => {
660 addClass(this.renderer, this.element, this.dragActiveClass);
661 const droppableElement = {
662 updateCache: true,
663 };
664 const deregisterScrollListener = this.renderer.listen(this.scrollContainer
665 ? this.scrollContainer.elementRef.nativeElement
666 : 'window', 'scroll', () => {
667 droppableElement.updateCache = true;
668 });
669 let currentDragEvent;
670 const overlaps$ = drag$.pipe(map(({ clientX, clientY, dropData, target }) => {
671 currentDragEvent = { clientX, clientY, dropData, target };
672 if (droppableElement.updateCache) {
673 droppableElement.rect =
674 this.element.nativeElement.getBoundingClientRect();
675 if (this.scrollContainer) {
676 droppableElement.scrollContainerRect =
677 this.scrollContainer.elementRef.nativeElement.getBoundingClientRect();
678 }
679 droppableElement.updateCache = false;
680 }
681 const isWithinElement = isCoordinateWithinRectangle(clientX, clientY, droppableElement.rect);
682 const isDropAllowed = !this.validateDrop ||
683 this.validateDrop({ clientX, clientY, target, dropData });
684 if (droppableElement.scrollContainerRect) {
685 return (isWithinElement &&
686 isDropAllowed &&
687 isCoordinateWithinRectangle(clientX, clientY, droppableElement.scrollContainerRect));
688 }
689 else {
690 return isWithinElement && isDropAllowed;
691 }
692 }));
693 const overlapsChanged$ = overlaps$.pipe(distinctUntilChanged());
694 let dragOverActive; // TODO - see if there's a way of doing this via rxjs
695 overlapsChanged$
696 .pipe(filter((overlapsNow) => overlapsNow))
697 .subscribe(() => {
698 dragOverActive = true;
699 addClass(this.renderer, this.element, this.dragOverClass);
700 if (this.dragEnter.observers.length > 0) {
701 this.zone.run(() => {
702 this.dragEnter.next(currentDragEvent);
703 });
704 }
705 });
706 overlaps$.pipe(filter((overlapsNow) => overlapsNow)).subscribe(() => {
707 if (this.dragOver.observers.length > 0) {
708 this.zone.run(() => {
709 this.dragOver.next(currentDragEvent);
710 });
711 }
712 });
713 overlapsChanged$
714 .pipe(pairwise(), filter(([didOverlap, overlapsNow]) => didOverlap && !overlapsNow))
715 .subscribe(() => {
716 dragOverActive = false;
717 removeClass(this.renderer, this.element, this.dragOverClass);
718 if (this.dragLeave.observers.length > 0) {
719 this.zone.run(() => {
720 this.dragLeave.next(currentDragEvent);
721 });
722 }
723 });
724 drag$.subscribe({
725 complete: () => {
726 deregisterScrollListener();
727 removeClass(this.renderer, this.element, this.dragActiveClass);
728 if (dragOverActive) {
729 removeClass(this.renderer, this.element, this.dragOverClass);
730 if (this.drop.observers.length > 0) {
731 this.zone.run(() => {
732 this.drop.next(currentDragEvent);
733 });
734 }
735 }
736 },
737 });
738 });
739 }
740 ngOnDestroy() {
741 if (this.currentDragSubscription) {
742 this.currentDragSubscription.unsubscribe();
743 }
744 }
745}
746DroppableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DroppableDirective, deps: [{ token: i0.ElementRef }, { token: DraggableHelper }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: DraggableScrollContainerDirective, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
747DroppableDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.3", type: DroppableDirective, selector: "[mwlDroppable]", inputs: { dragOverClass: "dragOverClass", dragActiveClass: "dragActiveClass", validateDrop: "validateDrop" }, outputs: { dragEnter: "dragEnter", dragLeave: "dragLeave", dragOver: "dragOver", drop: "drop" }, ngImport: i0 });
748i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DroppableDirective, decorators: [{
749 type: Directive,
750 args: [{
751 selector: '[mwlDroppable]',
752 }]
753 }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: DraggableHelper }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: DraggableScrollContainerDirective, decorators: [{
754 type: Optional
755 }] }]; }, propDecorators: { dragOverClass: [{
756 type: Input
757 }], dragActiveClass: [{
758 type: Input
759 }], validateDrop: [{
760 type: Input
761 }], dragEnter: [{
762 type: Output
763 }], dragLeave: [{
764 type: Output
765 }], dragOver: [{
766 type: Output
767 }], drop: [{
768 type: Output
769 }] } });
770
771class DragAndDropModule {
772}
773DragAndDropModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
774DragAndDropModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, declarations: [DraggableDirective,
775 DroppableDirective,
776 DraggableScrollContainerDirective], exports: [DraggableDirective,
777 DroppableDirective,
778 DraggableScrollContainerDirective] });
779DragAndDropModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule });
780i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, decorators: [{
781 type: NgModule,
782 args: [{
783 declarations: [
784 DraggableDirective,
785 DroppableDirective,
786 DraggableScrollContainerDirective,
787 ],
788 exports: [
789 DraggableDirective,
790 DroppableDirective,
791 DraggableScrollContainerDirective,
792 ],
793 }]
794 }] });
795
796/*
797 * Public API Surface of angular-draggable-droppable
798 */
799
800/**
801 * Generated bundle index. Do not edit.
802 */
803
804export { DragAndDropModule, DraggableDirective, DraggableScrollContainerDirective, DroppableDirective };
805//# sourceMappingURL=angular-draggable-droppable.mjs.map
806//# sourceMappingURL=angular-draggable-droppable.mjs.map