[![npm](https://img.shields.io/npm/v/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop) [![npm (next)](https://img.shields.io/npm/v/ngx-drag-drop/next.svg)](https://www.npmjs.com/package/ngx-drag-drop) [![NpmLicense](https://img.shields.io/npm/l/ngx-drag-drop.svg)](https://www.npmjs.com/package/ngx-drag-drop) [![GitHub issues](https://img.shields.io/github/issues/reppners/ngx-drag-drop.svg)](https://github.com/reppners/ngx-drag-drop/issues) [![Twitter](https://img.shields.io/twitter/url/https/github.com/reppners/ngx-drag-drop.svg?style=social)](https://twitter.com/intent/tweet?text=Angular%20drag%20and%20drop%20with%20ease:&url=https://github.com/reppners/ngx-drag-drop) # NgxDragDrop [_Demo_](https://reppners.github.io/ngx-drag-drop/) / [_StackBlitz Issue Template_](https://stackblitz.com/edit/ngx-drag-drop-issue-template) `npm install ngx-drag-drop --save` **Angular directives for declarative drag and drop using the HTML5 Drag-And-Drop API** - sortable lists by using placeholder element (vertical and horizontal) - nestable - dropzones optionally support external/native draggables (img, txt, file) - conditional drag/drop - typed drag/drop - utilize [EffectAllowed](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed) - custom CSS classes - touch support by using a [polyfill](#touch-support) - [AOT](https://angular.io/guide/aot-compiler) compatible Port of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists) but without the lists :wink: This has `dropzones` though :+1: The idea is that the directive does not handle lists internally so the `dndDropzone` can be general purpose. ## Usage `app.component.html` ```HTML
HANDLE
draggable ({{draggable.effectAllowed}}) DISABLED
DRAG_IMAGE
dropzone
placeholder
``` `app.component` ```JS import {Component} from '@angular/core'; import {DndDropEvent} from 'ngx-drag-drop'; @Component() export class AppComponent { draggable = { // note that data is handled with JSON.stringify/JSON.parse // only set simple data or POJO's as methods will be lost data: "myDragData", effectAllowed: "all", disable: false, handle: false }; onDragStart(event: DragEvent) { console.log("drag started", JSON.stringify(event, null, 2)); } onDragEnd(event: DragEvent) { console.log("drag ended", JSON.stringify(event, null, 2)); } onDraggableCopied(event: DragEvent) { console.log("draggable copied", JSON.stringify(event, null, 2)); } onDraggableLinked(event: DragEvent) { console.log("draggable linked", JSON.stringify(event, null, 2)); } onDraggableMoved(event: DragEvent) { console.log("draggable moved", JSON.stringify(event, null, 2)); } onDragCanceled(event: DragEvent) { console.log("drag cancelled", JSON.stringify(event, null, 2)); } onDragover(event: DragEvent) { console.log("dragover", JSON.stringify(event, null, 2)); } onDrop(event: DndDropEvent) { console.log("dropped", JSON.stringify(event, null, 2)); } } ``` `app.module` ```JS import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {DndModule} from 'ngx-drag-drop'; import {AppComponent} from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, DndModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ``` ## API ```TS // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect export type DropEffect = "move" | "copy" | "link" | "none"; // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed export type EffectAllowed = DropEffect | "copyMove" | "copyLink" | "linkMove" | "all"; ``` ```TS export type DndDragImageOffsetFunction = ( event:DragEvent, dragImage:Element ) => { x:number, y:number }; @Directive( { selector: "[dndDraggable]" } ) export declare class DndDraggableDirective { // the data attached to the drag dndDraggable: any; // the allowed drop effect dndEffectAllowed: EffectAllowed; // optionally set the type of dragged data to restrict dropping on compatible dropzones dndType?: string; // conditionally disable the draggability dndDisableIf: boolean; dndDisableDragIf: boolean; // set a custom class that is applied while dragging dndDraggingClass: string = "dndDragging"; // set a custom class that is applied to only the src element while dragging dndDraggingSourceClass: string = "dndDraggingSource"; // set the class that is applied when draggable is disabled by [dndDisableIf] dndDraggableDisabledClass = "dndDraggableDisabled"; // enables to set a function for calculating custom dragimage offset dndDragImageOffsetFunction:DndDragImageOffsetFunction = calculateDragImageOffset; // emits on drag start readonly dndStart: EventEmitter; // emits on drag readonly dndDrag: EventEmitter; // emits on drag end readonly dndEnd: EventEmitter; // emits when the dragged item has been dropped with effect "move" readonly dndMoved: EventEmitter; // emits when the dragged item has been dropped with effect "copy" readonly dndCopied: EventEmitter; // emits when the dragged item has been dropped with effect "link" readonly dndLinked: EventEmitter; // emits when the drag is canceled readonly dndCanceled: EventEmitter; } ``` ```TS export interface DndDropEvent { // the original drag event event: DragEvent; // the actual drop effect dropEffect: DropEffect; // true if the drag did not origin from a [dndDraggable] isExternal:boolean; // the data set on the [dndDraggable] that started the drag // for external drags use the event property which contains the original drop event as this will be undefined data?: any; // the index where the draggable was dropped in a dropzone // set only when using a placeholder index?: number; // if the dndType input on dndDraggable was set // it will be transported here type?: any; } @Directive( { selector: "[dndDropzone]" } ) export declare class DndDropzoneDirective { // optionally restrict the allowed types dndDropzone?: string[]; // set the allowed drop effect dndEffectAllowed: EffectAllowed; // conditionally disable the dropzone dndDisableIf: boolean; dndDisableDropIf: boolean; // if draggables that are not [dndDraggable] are allowed to be dropped // set to true if dragged text, images or files should be handled dndAllowExternal: boolean; // if its a horizontal list this influences how the placeholder position // is calculated dndHorizontal: boolean; // set the class applied to the dropzone // when a draggable is dragged over it dndDragoverClass: string = "dndDragover"; // set the class applied to the dropzone // when the dropzone is disabled by [dndDisableIf] dndDropzoneDisabledClass = "dndDropzoneDisabled"; // emits when a draggable is dragged over the dropzone readonly dndDragover: EventEmitter; // emits on successful drop readonly dndDrop: EventEmitter; } ``` ## Touch support Install the `mobile-drag-drop` module available on npm. Add the following lines to your js code ```JS import { polyfill } from 'mobile-drag-drop'; // optional import of scroll behaviour import { scrollBehaviourDragImageTranslateOverride } from "mobile-drag-drop/scroll-behaviour"; polyfill( { // use this to make use of the scroll behaviour dragImageTranslateOverride: scrollBehaviourDragImageTranslateOverride } ); // workaround to make scroll prevent work in iOS Safari > 10 try { window.addEventListener( "touchmove", function() { }, { passive: false } ); } catch(e){} ``` For more info on the polyfill check it out on GitHub https://github.com/timruffles/mobile-drag-drop ## Known issues ### Firefox - Beware that Firefox does not support dragging on `