282 lines
37 KiB
JavaScript
282 lines
37 KiB
JavaScript
|
import { ContentChild, Directive, EventEmitter, HostListener, Input, Output, } from '@angular/core';
|
||
|
import { getDndType, getDropEffect, isExternalDrag, setDropEffect, } from './dnd-state';
|
||
|
import { getDirectChildElement, getDropData, shouldPositionPlaceholderBeforeElement, } from './dnd-utils';
|
||
|
import * as i0 from "@angular/core";
|
||
|
class DndPlaceholderRefDirective {
|
||
|
elementRef;
|
||
|
constructor(elementRef) {
|
||
|
this.elementRef = elementRef;
|
||
|
}
|
||
|
ngOnInit() {
|
||
|
// placeholder has to be "invisible" to the cursor, or it would interfere with the dragover detection for the same dropzone
|
||
|
this.elementRef.nativeElement.style.pointerEvents = 'none';
|
||
|
}
|
||
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: DndPlaceholderRefDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
||
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.1", type: DndPlaceholderRefDirective, isStandalone: true, selector: "[dndPlaceholderRef]", ngImport: i0 });
|
||
|
}
|
||
|
export { DndPlaceholderRefDirective };
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: DndPlaceholderRefDirective, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{ selector: '[dndPlaceholderRef]', standalone: true }]
|
||
|
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; } });
|
||
|
class DndDropzoneDirective {
|
||
|
ngZone;
|
||
|
elementRef;
|
||
|
renderer;
|
||
|
dndDropzone = '';
|
||
|
dndEffectAllowed = 'uninitialized';
|
||
|
dndAllowExternal = false;
|
||
|
dndHorizontal = false;
|
||
|
dndDragoverClass = 'dndDragover';
|
||
|
dndDropzoneDisabledClass = 'dndDropzoneDisabled';
|
||
|
dndDragover = new EventEmitter();
|
||
|
dndDrop = new EventEmitter();
|
||
|
dndPlaceholderRef;
|
||
|
placeholder = null;
|
||
|
disabled = false;
|
||
|
constructor(ngZone, elementRef, renderer) {
|
||
|
this.ngZone = ngZone;
|
||
|
this.elementRef = elementRef;
|
||
|
this.renderer = renderer;
|
||
|
}
|
||
|
set dndDisableIf(value) {
|
||
|
this.disabled = value;
|
||
|
if (this.disabled) {
|
||
|
this.renderer.addClass(this.elementRef.nativeElement, this.dndDropzoneDisabledClass);
|
||
|
}
|
||
|
else {
|
||
|
this.renderer.removeClass(this.elementRef.nativeElement, this.dndDropzoneDisabledClass);
|
||
|
}
|
||
|
}
|
||
|
set dndDisableDropIf(value) {
|
||
|
this.dndDisableIf = value;
|
||
|
}
|
||
|
ngAfterViewInit() {
|
||
|
this.placeholder = this.tryGetPlaceholder();
|
||
|
this.removePlaceholderFromDOM();
|
||
|
this.ngZone.runOutsideAngular(() => {
|
||
|
this.elementRef.nativeElement.addEventListener('dragenter', this.dragEnterEventHandler);
|
||
|
this.elementRef.nativeElement.addEventListener('dragover', this.dragOverEventHandler);
|
||
|
this.elementRef.nativeElement.addEventListener('dragleave', this.dragLeaveEventHandler);
|
||
|
});
|
||
|
}
|
||
|
ngOnDestroy() {
|
||
|
this.elementRef.nativeElement.removeEventListener('dragenter', this.dragEnterEventHandler);
|
||
|
this.elementRef.nativeElement.removeEventListener('dragover', this.dragOverEventHandler);
|
||
|
this.elementRef.nativeElement.removeEventListener('dragleave', this.dragLeaveEventHandler);
|
||
|
}
|
||
|
onDragEnter(event) {
|
||
|
// check if another dropzone is activated
|
||
|
if (event._dndDropzoneActive === true) {
|
||
|
this.cleanupDragoverState();
|
||
|
return;
|
||
|
}
|
||
|
// set as active if the target element is inside this dropzone
|
||
|
if (event._dndDropzoneActive == null) {
|
||
|
const newTarget = document.elementFromPoint(event.clientX, event.clientY);
|
||
|
if (this.elementRef.nativeElement.contains(newTarget)) {
|
||
|
event._dndDropzoneActive = true;
|
||
|
}
|
||
|
}
|
||
|
// check if this drag event is allowed to drop on this dropzone
|
||
|
const type = getDndType(event);
|
||
|
if (!this.isDropAllowed(type)) {
|
||
|
return;
|
||
|
}
|
||
|
// allow the dragenter
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
onDragOver(event) {
|
||
|
// With nested dropzones, we want to ignore this event if a child dropzone
|
||
|
// has already handled a dragover. Historically, event.stopPropagation() was
|
||
|
// used to prevent this bubbling, but that prevents any dragovers outside the
|
||
|
// ngx-drag-drop component, and stops other use cases such as scrolling on drag.
|
||
|
// Instead, we can check if the event was already prevented by a child and bail early.
|
||
|
if (event.defaultPrevented) {
|
||
|
return;
|
||
|
}
|
||
|
// check if this drag event is allowed to drop on this dropzone
|
||
|
const type = getDndType(event);
|
||
|
if (!this.isDropAllowed(type)) {
|
||
|
return;
|
||
|
}
|
||
|
this.checkAndUpdatePlaceholderPosition(event);
|
||
|
const dropEffect = getDropEffect(event, this.dndEffectAllowed);
|
||
|
if (dropEffect === 'none') {
|
||
|
this.cleanupDragoverState();
|
||
|
return;
|
||
|
}
|
||
|
// allow the dragover
|
||
|
event.preventDefault();
|
||
|
// set the drop effect
|
||
|
setDropEffect(event, dropEffect);
|
||
|
this.dndDragover.emit(event);
|
||
|
this.renderer.addClass(this.elementRef.nativeElement, this.dndDragoverClass);
|
||
|
}
|
||
|
onDrop(event) {
|
||
|
try {
|
||
|
// check if this drag event is allowed to drop on this dropzone
|
||
|
const type = getDndType(event);
|
||
|
if (!this.isDropAllowed(type)) {
|
||
|
return;
|
||
|
}
|
||
|
const data = getDropData(event, isExternalDrag());
|
||
|
if (!this.isDropAllowed(data.type)) {
|
||
|
return;
|
||
|
}
|
||
|
// signal custom drop handling
|
||
|
event.preventDefault();
|
||
|
const dropEffect = getDropEffect(event);
|
||
|
setDropEffect(event, dropEffect);
|
||
|
if (dropEffect === 'none') {
|
||
|
return;
|
||
|
}
|
||
|
const dropIndex = this.getPlaceholderIndex();
|
||
|
// if for whatever reason the placeholder is not present in the DOM but it should be there
|
||
|
// we don't allow/emit the drop event since it breaks the contract
|
||
|
// seems to only happen if drag and drop is executed faster than the DOM updates
|
||
|
if (dropIndex === -1) {
|
||
|
return;
|
||
|
}
|
||
|
this.dndDrop.emit({
|
||
|
event: event,
|
||
|
dropEffect: dropEffect,
|
||
|
isExternal: isExternalDrag(),
|
||
|
data: data.data,
|
||
|
index: dropIndex,
|
||
|
type: type,
|
||
|
});
|
||
|
event.stopPropagation();
|
||
|
}
|
||
|
finally {
|
||
|
this.cleanupDragoverState();
|
||
|
}
|
||
|
}
|
||
|
onDragLeave(event) {
|
||
|
event.preventDefault();
|
||
|
event.stopPropagation();
|
||
|
// check if still inside this dropzone and not yet handled by another dropzone
|
||
|
if (event._dndDropzoneActive == null) {
|
||
|
if (this.elementRef.nativeElement.contains(event.relatedTarget)) {
|
||
|
event._dndDropzoneActive = true;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
this.cleanupDragoverState();
|
||
|
// cleanup drop effect when leaving dropzone
|
||
|
setDropEffect(event, 'none');
|
||
|
}
|
||
|
dragEnterEventHandler = (event) => this.onDragEnter(event);
|
||
|
dragOverEventHandler = (event) => this.onDragOver(event);
|
||
|
dragLeaveEventHandler = (event) => this.onDragLeave(event);
|
||
|
isDropAllowed(type) {
|
||
|
// dropzone is disabled -> deny it
|
||
|
if (this.disabled) {
|
||
|
return false;
|
||
|
}
|
||
|
// if drag did not start from our directive
|
||
|
// and external drag sources are not allowed -> deny it
|
||
|
if (isExternalDrag() && !this.dndAllowExternal) {
|
||
|
return false;
|
||
|
}
|
||
|
// no filtering by types -> allow it
|
||
|
if (!this.dndDropzone) {
|
||
|
return true;
|
||
|
}
|
||
|
// no type set -> allow it
|
||
|
if (!type) {
|
||
|
return true;
|
||
|
}
|
||
|
if (!Array.isArray(this.dndDropzone)) {
|
||
|
throw new Error('dndDropzone: bound value to [dndDropzone] must be an array!');
|
||
|
}
|
||
|
// if dropzone contains type -> allow it
|
||
|
return this.dndDropzone.indexOf(type) !== -1;
|
||
|
}
|
||
|
tryGetPlaceholder() {
|
||
|
if (typeof this.dndPlaceholderRef !== 'undefined') {
|
||
|
return this.dndPlaceholderRef.elementRef.nativeElement;
|
||
|
}
|
||
|
// TODO nasty workaround needed because if ng-container / template is used @ContentChild() or DI will fail because
|
||
|
// of wrong context see angular bug https://github.com/angular/angular/issues/13517
|
||
|
return this.elementRef.nativeElement.querySelector('[dndPlaceholderRef]');
|
||
|
}
|
||
|
removePlaceholderFromDOM() {
|
||
|
if (this.placeholder !== null && this.placeholder.parentNode !== null) {
|
||
|
this.placeholder.parentNode.removeChild(this.placeholder);
|
||
|
}
|
||
|
}
|
||
|
checkAndUpdatePlaceholderPosition(event) {
|
||
|
if (this.placeholder === null) {
|
||
|
return;
|
||
|
}
|
||
|
// make sure the placeholder is in the DOM
|
||
|
if (this.placeholder.parentNode !== this.elementRef.nativeElement) {
|
||
|
this.renderer.appendChild(this.elementRef.nativeElement, this.placeholder);
|
||
|
}
|
||
|
// update the position if the event originates from a child element of the dropzone
|
||
|
const directChild = getDirectChildElement(this.elementRef.nativeElement, event.target);
|
||
|
// early exit if no direct child or direct child is placeholder
|
||
|
if (directChild === null || directChild === this.placeholder) {
|
||
|
return;
|
||
|
}
|
||
|
const positionPlaceholderBeforeDirectChild = shouldPositionPlaceholderBeforeElement(event, directChild, this.dndHorizontal);
|
||
|
if (positionPlaceholderBeforeDirectChild) {
|
||
|
// do insert before only if necessary
|
||
|
if (directChild.previousSibling !== this.placeholder) {
|
||
|
this.renderer.insertBefore(this.elementRef.nativeElement, this.placeholder, directChild);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// do insert after only if necessary
|
||
|
if (directChild.nextSibling !== this.placeholder) {
|
||
|
this.renderer.insertBefore(this.elementRef.nativeElement, this.placeholder, directChild.nextSibling);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
getPlaceholderIndex() {
|
||
|
if (this.placeholder === null) {
|
||
|
return undefined;
|
||
|
}
|
||
|
const element = this.elementRef.nativeElement;
|
||
|
return Array.prototype.indexOf.call(element.children, this.placeholder);
|
||
|
}
|
||
|
cleanupDragoverState() {
|
||
|
this.renderer.removeClass(this.elementRef.nativeElement, this.dndDragoverClass);
|
||
|
this.removePlaceholderFromDOM();
|
||
|
}
|
||
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: DndDropzoneDirective, deps: [{ token: i0.NgZone }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
|
||
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.1", type: DndDropzoneDirective, isStandalone: true, selector: "[dndDropzone]", inputs: { dndDropzone: "dndDropzone", dndEffectAllowed: "dndEffectAllowed", dndAllowExternal: "dndAllowExternal", dndHorizontal: "dndHorizontal", dndDragoverClass: "dndDragoverClass", dndDropzoneDisabledClass: "dndDropzoneDisabledClass", dndDisableIf: "dndDisableIf", dndDisableDropIf: "dndDisableDropIf" }, outputs: { dndDragover: "dndDragover", dndDrop: "dndDrop" }, host: { listeners: { "drop": "onDrop($event)" } }, queries: [{ propertyName: "dndPlaceholderRef", first: true, predicate: DndPlaceholderRefDirective, descendants: true }], ngImport: i0 });
|
||
|
}
|
||
|
export { DndDropzoneDirective };
|
||
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: DndDropzoneDirective, decorators: [{
|
||
|
type: Directive,
|
||
|
args: [{ selector: '[dndDropzone]', standalone: true }]
|
||
|
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { dndDropzone: [{
|
||
|
type: Input
|
||
|
}], dndEffectAllowed: [{
|
||
|
type: Input
|
||
|
}], dndAllowExternal: [{
|
||
|
type: Input
|
||
|
}], dndHorizontal: [{
|
||
|
type: Input
|
||
|
}], dndDragoverClass: [{
|
||
|
type: Input
|
||
|
}], dndDropzoneDisabledClass: [{
|
||
|
type: Input
|
||
|
}], dndDragover: [{
|
||
|
type: Output
|
||
|
}], dndDrop: [{
|
||
|
type: Output
|
||
|
}], dndPlaceholderRef: [{
|
||
|
type: ContentChild,
|
||
|
args: [DndPlaceholderRefDirective]
|
||
|
}], dndDisableIf: [{
|
||
|
type: Input
|
||
|
}], dndDisableDropIf: [{
|
||
|
type: Input
|
||
|
}], onDrop: [{
|
||
|
type: HostListener,
|
||
|
args: ['drop', ['$event']]
|
||
|
}] } });
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG5kLWRyb3B6b25lLmRpcmVjdGl2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL2RuZC9zcmMvbGliL2RuZC1kcm9wem9uZS5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUVMLFlBQVksRUFDWixTQUFTLEVBRVQsWUFBWSxFQUNaLFlBQVksRUFDWixLQUFLLEVBSUwsTUFBTSxHQUVQLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDTCxVQUFVLEVBQ1YsYUFBYSxFQUNiLGNBQWMsRUFDZCxhQUFhLEdBQ2QsTUFBTSxhQUFhLENBQUM7QUFFckIsT0FBTyxFQUdMLHFCQUFxQixFQUNyQixXQUFXLEVBQ1gsc0NBQXNDLEdBQ3ZDLE1BQU0sYUFBYSxDQUFDOztBQVdyQixNQUNhLDBCQUEwQjtJQUNUO0lBQTVCLFlBQTRCLFVBQW1DO1FBQW5DLGVBQVUsR0FBVixVQUFVLENBQXlCO0lBQUcsQ0FBQztJQUVuRSxRQUFRO1FBQ04sMkhBQTJIO1FBQzNILElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxhQUFhLEdBQUcsTUFBTSxDQUFDO0lBQzdELENBQUM7dUdBTlUsMEJBQTBCOzJGQUExQiwwQkFBMEI7O1NBQTFCLDBCQUEwQjsyRkFBMUIsMEJBQTBCO2tCQUR0QyxTQUFTO21CQUFDLEVBQUUsUUFBUSxFQUFFLHFCQUFxQixFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUU7O0FBVWhFLE1BQ2Esb0JBQW9CO0lBMkJyQjtJQUNBO0lBQ0E7SUE1QkQsV0FBVyxHQUFtQixFQUFFLENBQUM7SUFFakMsZ0JBQWdCLEdBQWtCLGVBQWUsQ0FBQztJQUVsRCxnQkFBZ0IsR0FBWSxLQUFLLENBQUM7SUFFbEMsYUFBYSxHQUFZLEtBQUssQ0FBQztJQUUvQixnQkFBZ0IsR0FBVyxhQUFhLENBQUM7SUFFekMsd0JBQXdCLEdBQUcscUJBQXFCLENBQUM7SUFFdkMsV0FB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
|