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 Object.assign(Object.assign({}, moveData), { x: moveData.transformX + scrollX, y: moveData.transformY + scrollY });
213 }), filter(({ x, y, transformX, transformY }) => !this.validateDrag ||
214 this.validateDrag({
215 x,
216 y,
217 transform: { x: transformX, y: transformY },
218 })), takeUntil(dragComplete$), share());
219 const dragStarted$ = pointerMove.pipe(take(1), share());
220 const dragEnded$ = pointerMove.pipe(takeLast(1), share());
221 dragStarted$.subscribe(({ clientX, clientY, x, y }) => {
222 if (this.dragStart.observers.length > 0) {
223 this.zone.run(() => {
224 this.dragStart.next({ cancelDrag$ });
225 });
226 }
227 this.scroller = autoScroll([
228 this.scrollContainer
229 ? this.scrollContainer.elementRef.nativeElement
230 : this.document.defaultView,
231 ], Object.assign(Object.assign({}, this.autoScroll), { autoScroll() {
232 return true;
233 } }));
234 addClass(this.renderer, this.element, this.dragActiveClass);
235 if (this.ghostDragEnabled) {
236 const rect = this.element.nativeElement.getBoundingClientRect();
237 const clone = this.element.nativeElement.cloneNode(true);
238 if (!this.showOriginalElementWhileDragging) {
239 this.renderer.setStyle(this.element.nativeElement, 'visibility', 'hidden');
240 }
241 if (this.ghostElementAppendTo) {
242 this.ghostElementAppendTo.appendChild(clone);
243 }
244 else {
245 this.element.nativeElement.parentNode.insertBefore(clone, this.element.nativeElement.nextSibling);
246 }
247 this.ghostElement = clone;
248 this.document.body.style.cursor = this.dragCursor;
249 this.setElementStyles(clone, {
250 position: 'fixed',
251 top: `${rect.top}px`,
252 left: `${rect.left}px`,
253 width: `${rect.width}px`,
254 height: `${rect.height}px`,
255 cursor: this.dragCursor,
256 margin: '0',
257 willChange: 'transform',
258 pointerEvents: 'none',
259 });
260 if (this.ghostElementTemplate) {
261 const viewRef = this.vcr.createEmbeddedView(this.ghostElementTemplate);
262 clone.innerHTML = '';
263 viewRef.rootNodes
264 .filter((node) => node instanceof Node)
265 .forEach((node) => {
266 clone.appendChild(node);
267 });
268 dragEnded$.subscribe(() => {
269 this.vcr.remove(this.vcr.indexOf(viewRef));
270 });
271 }
272 if (this.ghostElementCreated.observers.length > 0) {
273 this.zone.run(() => {
274 this.ghostElementCreated.emit({
275 clientX: clientX - x,
276 clientY: clientY - y,
277 element: clone,
278 });
279 });
280 }
281 dragEnded$.subscribe(() => {
282 clone.parentElement.removeChild(clone);
283 this.ghostElement = null;
284 this.renderer.setStyle(this.element.nativeElement, 'visibility', '');
285 });
286 }
287 this.draggableHelper.currentDrag.next(currentDrag$);
288 });
289 dragEnded$
290 .pipe(mergeMap((dragEndData) => {
291 const dragEndData$ = cancelDrag$.pipe(count(), take(1), map((calledCount) => (Object.assign(Object.assign({}, dragEndData), { dragCancelled: calledCount > 0 }))));
292 cancelDrag$.complete();
293 return dragEndData$;
294 }))
295 .subscribe(({ x, y, dragCancelled }) => {
296 this.scroller.destroy();
297 if (this.dragEnd.observers.length > 0) {
298 this.zone.run(() => {
299 this.dragEnd.next({ x, y, dragCancelled });
300 });
301 }
302 removeClass(this.renderer, this.element, this.dragActiveClass);
303 currentDrag$.complete();
304 });
305 merge(dragComplete$, dragEnded$)
306 .pipe(take(1))
307 .subscribe(() => {
308 requestAnimationFrame(() => {
309 this.document.head.removeChild(globalDragStyle);
310 });
311 });
312 return pointerMove;
313 }), share());
314 merge(pointerDragged$.pipe(take(1), map((value) => [, value])), pointerDragged$.pipe(pairwise()))
315 .pipe(filter(([previous, next]) => {
316 if (!previous) {
317 return true;
318 }
319 return previous.x !== next.x || previous.y !== next.y;
320 }), map(([previous, next]) => next))
321 .subscribe(({ x, y, currentDrag$, clientX, clientY, transformX, transformY, target, }) => {
322 if (this.dragging.observers.length > 0) {
323 this.zone.run(() => {
324 this.dragging.next({ x, y });
325 });
326 }
327 requestAnimationFrame(() => {
328 if (this.ghostElement) {
329 const transform = `translate3d(${transformX}px, ${transformY}px, 0px)`;
330 this.setElementStyles(this.ghostElement, {
331 transform,
332 '-webkit-transform': transform,
333 '-ms-transform': transform,
334 '-moz-transform': transform,
335 '-o-transform': transform,
336 });
337 }
338 });
339 currentDrag$.next({
340 clientX,
341 clientY,
342 dropData: this.dropData,
343 target,
344 });
345 });
346 }
347 ngOnChanges(changes) {
348 if (changes.dragAxis) {
349 this.checkEventListeners();
350 }
351 }
352 ngOnDestroy() {
353 this.unsubscribeEventListeners();
354 this.pointerDown$.complete();
355 this.pointerMove$.complete();
356 this.pointerUp$.complete();
357 this.destroy$.next();
358 }
359 checkEventListeners() {
360 const canDrag = this.canDrag();
361 const hasEventListeners = Object.keys(this.eventListenerSubscriptions).length > 0;
362 if (canDrag && !hasEventListeners) {
363 this.zone.runOutsideAngular(() => {
364 this.eventListenerSubscriptions.mousedown = this.renderer.listen(this.element.nativeElement, 'mousedown', (event) => {
365 this.onMouseDown(event);
366 });
367 this.eventListenerSubscriptions.mouseup = this.renderer.listen('document', 'mouseup', (event) => {
368 this.onMouseUp(event);
369 });
370 this.eventListenerSubscriptions.touchstart = this.renderer.listen(this.element.nativeElement, 'touchstart', (event) => {
371 this.onTouchStart(event);
372 });
373 this.eventListenerSubscriptions.touchend = this.renderer.listen('document', 'touchend', (event) => {
374 this.onTouchEnd(event);
375 });
376 this.eventListenerSubscriptions.touchcancel = this.renderer.listen('document', 'touchcancel', (event) => {
377 this.onTouchEnd(event);
378 });
379 this.eventListenerSubscriptions.mouseenter = this.renderer.listen(this.element.nativeElement, 'mouseenter', () => {
380 this.onMouseEnter();
381 });
382 this.eventListenerSubscriptions.mouseleave = this.renderer.listen(this.element.nativeElement, 'mouseleave', () => {
383 this.onMouseLeave();
384 });
385 });
386 }
387 else if (!canDrag && hasEventListeners) {
388 this.unsubscribeEventListeners();
389 }
390 }
391 onMouseDown(event) {
392 if (event.button === 0) {
393 if (!this.eventListenerSubscriptions.mousemove) {
394 this.eventListenerSubscriptions.mousemove = this.renderer.listen('document', 'mousemove', (mouseMoveEvent) => {
395 this.pointerMove$.next({
396 event: mouseMoveEvent,
397 clientX: mouseMoveEvent.clientX,
398 clientY: mouseMoveEvent.clientY,
399 });
400 });
401 }
402 this.pointerDown$.next({
403 event,
404 clientX: event.clientX,
405 clientY: event.clientY,
406 });
407 }
408 }
409 onMouseUp(event) {
410 if (event.button === 0) {
411 if (this.eventListenerSubscriptions.mousemove) {
412 this.eventListenerSubscriptions.mousemove();
413 delete this.eventListenerSubscriptions.mousemove;
414 }
415 this.pointerUp$.next({
416 event,
417 clientX: event.clientX,
418 clientY: event.clientY,
419 });
420 }
421 }
422 onTouchStart(event) {
423 let startScrollPosition;
424 let isDragActivated;
425 let hasContainerScrollbar;
426 if (this.touchStartLongPress) {
427 this.timeLongPress.timerBegin = Date.now();
428 isDragActivated = false;
429 hasContainerScrollbar = this.hasScrollbar();
430 startScrollPosition = this.getScrollPosition();
431 }
432 if (!this.eventListenerSubscriptions.touchmove) {
433 const contextMenuListener = fromEvent(this.document, 'contextmenu').subscribe((e) => {
434 e.preventDefault();
435 });
436 const touchMoveListener = fromEvent(this.document, 'touchmove', {
437 passive: false,
438 }).subscribe((touchMoveEvent) => {
439 if (this.touchStartLongPress &&
440 !isDragActivated &&
441 hasContainerScrollbar) {
442 isDragActivated = this.shouldBeginDrag(event, touchMoveEvent, startScrollPosition);
443 }
444 if (!this.touchStartLongPress ||
445 !hasContainerScrollbar ||
446 isDragActivated) {
447 touchMoveEvent.preventDefault();
448 this.pointerMove$.next({
449 event: touchMoveEvent,
450 clientX: touchMoveEvent.targetTouches[0].clientX,
451 clientY: touchMoveEvent.targetTouches[0].clientY,
452 });
453 }
454 });
455 this.eventListenerSubscriptions.touchmove = () => {
456 contextMenuListener.unsubscribe();
457 touchMoveListener.unsubscribe();
458 };
459 }
460 this.pointerDown$.next({
461 event,
462 clientX: event.touches[0].clientX,
463 clientY: event.touches[0].clientY,
464 });
465 }
466 onTouchEnd(event) {
467 if (this.eventListenerSubscriptions.touchmove) {
468 this.eventListenerSubscriptions.touchmove();
469 delete this.eventListenerSubscriptions.touchmove;
470 if (this.touchStartLongPress) {
471 this.enableScroll();
472 }
473 }
474 this.pointerUp$.next({
475 event,
476 clientX: event.changedTouches[0].clientX,
477 clientY: event.changedTouches[0].clientY,
478 });
479 }
480 onMouseEnter() {
481 this.setCursor(this.dragCursor);
482 }
483 onMouseLeave() {
484 this.setCursor('');
485 }
486 canDrag() {
487 return this.dragAxis.x || this.dragAxis.y;
488 }
489 setCursor(value) {
490 if (!this.eventListenerSubscriptions.mousemove) {
491 this.renderer.setStyle(this.element.nativeElement, 'cursor', value);
492 }
493 }
494 unsubscribeEventListeners() {
495 Object.keys(this.eventListenerSubscriptions).forEach((type) => {
496 this.eventListenerSubscriptions[type]();
497 delete this.eventListenerSubscriptions[type];
498 });
499 }
500 setElementStyles(element, styles) {
501 Object.keys(styles).forEach((key) => {
502 this.renderer.setStyle(element, key, styles[key]);
503 });
504 }
505 getScrollElement() {
506 if (this.scrollContainer) {
507 return this.scrollContainer.elementRef.nativeElement;
508 }
509 else {
510 return this.document.body;
511 }
512 }
513 getScrollPosition() {
514 if (this.scrollContainer) {
515 return {
516 top: this.scrollContainer.elementRef.nativeElement.scrollTop,
517 left: this.scrollContainer.elementRef.nativeElement.scrollLeft,
518 };
519 }
520 else {
521 return {
522 top: window.pageYOffset || this.document.documentElement.scrollTop,
523 left: window.pageXOffset || this.document.documentElement.scrollLeft,
524 };
525 }
526 }
527 shouldBeginDrag(event, touchMoveEvent, startScrollPosition) {
528 const moveScrollPosition = this.getScrollPosition();
529 const deltaScroll = {
530 top: Math.abs(moveScrollPosition.top - startScrollPosition.top),
531 left: Math.abs(moveScrollPosition.left - startScrollPosition.left),
532 };
533 const deltaX = Math.abs(touchMoveEvent.targetTouches[0].clientX - event.touches[0].clientX) - deltaScroll.left;
534 const deltaY = Math.abs(touchMoveEvent.targetTouches[0].clientY - event.touches[0].clientY) - deltaScroll.top;
535 const deltaTotal = deltaX + deltaY;
536 const longPressConfig = this.touchStartLongPress;
537 if (deltaTotal > longPressConfig.delta ||
538 deltaScroll.top > 0 ||
539 deltaScroll.left > 0) {
540 this.timeLongPress.timerBegin = Date.now();
541 }
542 this.timeLongPress.timerEnd = Date.now();
543 const duration = this.timeLongPress.timerEnd - this.timeLongPress.timerBegin;
544 if (duration >= longPressConfig.delay) {
545 this.disableScroll();
546 return true;
547 }
548 return false;
549 }
550 enableScroll() {
551 if (this.scrollContainer) {
552 this.renderer.setStyle(this.scrollContainer.elementRef.nativeElement, 'overflow', '');
553 }
554 this.renderer.setStyle(this.document.body, 'overflow', '');
555 }
556 disableScroll() {
557 /* istanbul ignore next */
558 if (this.scrollContainer) {
559 this.renderer.setStyle(this.scrollContainer.elementRef.nativeElement, 'overflow', 'hidden');
560 }
561 this.renderer.setStyle(this.document.body, 'overflow', 'hidden');
562 }
563 hasScrollbar() {
564 const scrollContainer = this.getScrollElement();
565 const containerHasHorizontalScroll = scrollContainer.scrollWidth > scrollContainer.clientWidth;
566 const containerHasVerticalScroll = scrollContainer.scrollHeight > scrollContainer.clientHeight;
567 return containerHasHorizontalScroll || containerHasVerticalScroll;
568 }
569}
570DraggableDirective.ɵ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 });
571DraggableDirective.ɵ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 });
572i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DraggableDirective, decorators: [{
573 type: Directive,
574 args: [{
575 selector: '[mwlDraggable]',
576 }]
577 }], ctorParameters: function () {
578 return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: DraggableHelper }, { type: i0.NgZone }, { type: i0.ViewContainerRef }, { type: DraggableScrollContainerDirective, decorators: [{
579 type: Optional
580 }] }, { type: undefined, decorators: [{
581 type: Inject,
582 args: [DOCUMENT]
583 }] }];
584 }, propDecorators: { dropData: [{
585 type: Input
586 }], dragAxis: [{
587 type: Input
588 }], dragSnapGrid: [{
589 type: Input
590 }], ghostDragEnabled: [{
591 type: Input
592 }], showOriginalElementWhileDragging: [{
593 type: Input
594 }], validateDrag: [{
595 type: Input
596 }], dragCursor: [{
597 type: Input
598 }], dragActiveClass: [{
599 type: Input
600 }], ghostElementAppendTo: [{
601 type: Input
602 }], ghostElementTemplate: [{
603 type: Input
604 }], touchStartLongPress: [{
605 type: Input
606 }], autoScroll: [{
607 type: Input
608 }], dragPointerDown: [{
609 type: Output
610 }], dragStart: [{
611 type: Output
612 }], ghostElementCreated: [{
613 type: Output
614 }], dragging: [{
615 type: Output
616 }], dragEnd: [{
617 type: Output
618 }] } });
619
620function isCoordinateWithinRectangle(clientX, clientY, rect) {
621 return (clientX >= rect.left &&
622 clientX <= rect.right &&
623 clientY >= rect.top &&
624 clientY <= rect.bottom);
625}
626class DroppableDirective {
627 constructor(element, draggableHelper, zone, renderer, scrollContainer) {
628 this.element = element;
629 this.draggableHelper = draggableHelper;
630 this.zone = zone;
631 this.renderer = renderer;
632 this.scrollContainer = scrollContainer;
633 /**
634 * Called when a draggable element starts overlapping the element
635 */
636 this.dragEnter = new EventEmitter();
637 /**
638 * Called when a draggable element stops overlapping the element
639 */
640 this.dragLeave = new EventEmitter();
641 /**
642 * Called when a draggable element is moved over the element
643 */
644 this.dragOver = new EventEmitter();
645 /**
646 * Called when a draggable element is dropped on this element
647 */
648 this.drop = new EventEmitter(); // eslint-disable-line @angular-eslint/no-output-native
649 }
650 ngOnInit() {
651 this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag$) => {
652 addClass(this.renderer, this.element, this.dragActiveClass);
653 const droppableElement = {
654 updateCache: true,
655 };
656 const deregisterScrollListener = this.renderer.listen(this.scrollContainer
657 ? this.scrollContainer.elementRef.nativeElement
658 : 'window', 'scroll', () => {
659 droppableElement.updateCache = true;
660 });
661 let currentDragEvent;
662 const overlaps$ = drag$.pipe(map(({ clientX, clientY, dropData, target }) => {
663 currentDragEvent = { clientX, clientY, dropData, target };
664 if (droppableElement.updateCache) {
665 droppableElement.rect =
666 this.element.nativeElement.getBoundingClientRect();
667 if (this.scrollContainer) {
668 droppableElement.scrollContainerRect =
669 this.scrollContainer.elementRef.nativeElement.getBoundingClientRect();
670 }
671 droppableElement.updateCache = false;
672 }
673 const isWithinElement = isCoordinateWithinRectangle(clientX, clientY, droppableElement.rect);
674 const isDropAllowed = !this.validateDrop ||
675 this.validateDrop({ clientX, clientY, target, dropData });
676 if (droppableElement.scrollContainerRect) {
677 return (isWithinElement &&
678 isDropAllowed &&
679 isCoordinateWithinRectangle(clientX, clientY, droppableElement.scrollContainerRect));
680 }
681 else {
682 return isWithinElement && isDropAllowed;
683 }
684 }));
685 const overlapsChanged$ = overlaps$.pipe(distinctUntilChanged());
686 let dragOverActive; // TODO - see if there's a way of doing this via rxjs
687 overlapsChanged$
688 .pipe(filter((overlapsNow) => overlapsNow))
689 .subscribe(() => {
690 dragOverActive = true;
691 addClass(this.renderer, this.element, this.dragOverClass);
692 if (this.dragEnter.observers.length > 0) {
693 this.zone.run(() => {
694 this.dragEnter.next(currentDragEvent);
695 });
696 }
697 });
698 overlaps$.pipe(filter((overlapsNow) => overlapsNow)).subscribe(() => {
699 if (this.dragOver.observers.length > 0) {
700 this.zone.run(() => {
701 this.dragOver.next(currentDragEvent);
702 });
703 }
704 });
705 overlapsChanged$
706 .pipe(pairwise(), filter(([didOverlap, overlapsNow]) => didOverlap && !overlapsNow))
707 .subscribe(() => {
708 dragOverActive = false;
709 removeClass(this.renderer, this.element, this.dragOverClass);
710 if (this.dragLeave.observers.length > 0) {
711 this.zone.run(() => {
712 this.dragLeave.next(currentDragEvent);
713 });
714 }
715 });
716 drag$.subscribe({
717 complete: () => {
718 deregisterScrollListener();
719 removeClass(this.renderer, this.element, this.dragActiveClass);
720 if (dragOverActive) {
721 removeClass(this.renderer, this.element, this.dragOverClass);
722 if (this.drop.observers.length > 0) {
723 this.zone.run(() => {
724 this.drop.next(currentDragEvent);
725 });
726 }
727 }
728 },
729 });
730 });
731 }
732 ngOnDestroy() {
733 if (this.currentDragSubscription) {
734 this.currentDragSubscription.unsubscribe();
735 }
736 }
737}
738DroppableDirective.ɵ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 });
739DroppableDirective.ɵ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 });
740i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DroppableDirective, decorators: [{
741 type: Directive,
742 args: [{
743 selector: '[mwlDroppable]',
744 }]
745 }], ctorParameters: function () {
746 return [{ type: i0.ElementRef }, { type: DraggableHelper }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: DraggableScrollContainerDirective, decorators: [{
747 type: Optional
748 }] }];
749 }, propDecorators: { dragOverClass: [{
750 type: Input
751 }], dragActiveClass: [{
752 type: Input
753 }], validateDrop: [{
754 type: Input
755 }], dragEnter: [{
756 type: Output
757 }], dragLeave: [{
758 type: Output
759 }], dragOver: [{
760 type: Output
761 }], drop: [{
762 type: Output
763 }] } });
764
765class DragAndDropModule {
766}
767DragAndDropModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
768DragAndDropModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, declarations: [DraggableDirective,
769 DroppableDirective,
770 DraggableScrollContainerDirective], exports: [DraggableDirective,
771 DroppableDirective,
772 DraggableScrollContainerDirective] });
773DragAndDropModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule });
774i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: DragAndDropModule, decorators: [{
775 type: NgModule,
776 args: [{
777 declarations: [
778 DraggableDirective,
779 DroppableDirective,
780 DraggableScrollContainerDirective,
781 ],
782 exports: [
783 DraggableDirective,
784 DroppableDirective,
785 DraggableScrollContainerDirective,
786 ],
787 }]
788 }] });
789
790/*
791 * Public API Surface of angular-draggable-droppable
792 */
793
794/**
795 * Generated bundle index. Do not edit.
796 */
797
798export { DragAndDropModule, DraggableDirective, DraggableScrollContainerDirective, DroppableDirective };
799//# sourceMappingURL=angular-draggable-droppable.mjs.map
800//# sourceMappingURL=angular-draggable-droppable.mjs.map