UNPKG

ngx-drag-drop

Version:

Angular directives using the native HTML Drag And Drop API

338 lines (242 loc) 9.75 kB
[![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 <!--a draggable element--> <div [dndDraggable]="draggable.data" [dndEffectAllowed]="draggable.effectAllowed" [dndDisableIf]="draggable.disable" (dndStart)="onDragStart($event)" (dndCopied)="onDraggableCopied($event)" (dndLinked)="onDraggableLinked($event)" (dndMoved)="onDraggableMoved($event)" (dndCanceled)="onDragCanceled($event)" (dndEnd)="onDragEnd($event)"> <!--if [dndHandle] is used inside dndDraggable drag can only start from the handle--> <div *ngIf="draggable.handle" dndHandle>HANDLE </div> draggable ({{draggable.effectAllowed}}) <span [hidden]="!draggable.disable">DISABLED</span> <!--optionally select a child element as drag image--> <div dndDragImageRef>DRAG_IMAGE</div> </div> <!--a dropzone--> <!--to allow dropping content that is not [dndDraggable] set dndAllowExternal to true--> <section dndDropzone (dndDragover)="onDragover($event)" (dndDrop)="onDrop($event)"> dropzone <!--optional placeholder element for dropzone--> <!--will be removed from DOM on init--> <div style="border: 1px orangered solid; border-radius: 5px; padding: 15px;" dndPlaceholderRef> placeholder </div> </section> ``` `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<DragEvent>; // emits on drag readonly dndDrag: EventEmitter<DragEvent>; // emits on drag end readonly dndEnd: EventEmitter<DragEvent>; // emits when the dragged item has been dropped with effect "move" readonly dndMoved: EventEmitter<DragEvent>; // emits when the dragged item has been dropped with effect "copy" readonly dndCopied: EventEmitter<DragEvent>; // emits when the dragged item has been dropped with effect "link" readonly dndLinked: EventEmitter<DragEvent>; // emits when the drag is canceled readonly dndCanceled: EventEmitter<DragEvent>; } ``` ```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<DragEvent>; // emits on successful drop readonly dndDrop: EventEmitter<DndDropEvent>; } ``` ## 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 `<button>` elements. - `<button [dndDraggable]>` and `<button [dndHandler]>` won't work. - See https://bugzilla.mozilla.org/show_bug.cgi?id=568313 ## Why? HTML Drag-And-Drop API implementations are not behaving the same way across browsers. The directives contained in this module enable declarative drag and drop that "just works" across browsers in a consistent way. Credits go to the author and contributors of [angular-drag-drop-lists](https://github.com/marceljuenemann/angular-drag-and-drop-lists).