Demo / StackBlitz Issue Template
npm install ngx-drag-drop
# or
pnpm add ngx-drag-drop
Angular directives for declarative drag and drop using the HTML5 Drag-And-Drop API
Port of angular-drag-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.
Starting with v13, the library major version matches the Angular major version.
| Angular | ngx-drag-drop |
|---|---|
| 22.x | 22.x |
| 21.x | 21.x |
| 20.x | 20.x |
| 19.x | 19.x |
| 18.x | 18.x |
| 17.x | 17.x |
| 16.x | 16.x |
| 15.x | 15.x |
| 14.x | 14.x |
| 13.x | 13.x |
For older Angular versions (v4–v12), use ngx-drag-drop v2.x.
app.component.html
HANDLE
draggable ({{draggable.effectAllowed}}) <span [hidden]="!draggable.disable">DISABLED</span>
DRAG_IMAGE
<section dndDropzone
(dndDragover)="onDragover($event)"
(dndDrop)="onDrop($event)">
dropzone
placeholder
</section>
app.component
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
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 {
}
// 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";
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>;
}
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>;
}
This library uses the native HTML5 Drag and Drop API, which is not supported on most mobile browsers (including iOS Safari). Touch support requires a polyfill that translates touch events into drag events. This approach has known limitations and the experience may not be reliable on all devices.
To enable basic touch support, install the @dragdroptouch/drag-drop-touch polyfill:
import { enableDragDropTouch } from "@dragdroptouch/drag-drop-touch";
enableDragDropTouch();
For more info on the polyfill check it out on GitHub https://github.com/drag-drop-touch-js/dragdroptouch
<button> elements.<button [dndDraggable]> and <button [dndHandler]> won't work.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.
This project was generated with Angular CLI.
See https://angular.io/guide/creating-libraries
pnpm run watch:lib for hacking on libraryprojects/dnd/package.jsonpnpm run build:libpnpm run publish:stable (use pnpm run publish:next for pre-releases)pnpm run build:lib to current version of lib available to the demopnpm run start:docsThe demo site is automatically deployed to GitHub Pages on every push to master via GitHub Actions.
$ claude mcp add ngx-drag-drop \
-- python -m otcore.mcp_server <graph>