包详细信息

ngx-drag-drop

reppners184.5kBSD-3-Clause19.0.0

Angular directives using the native HTML Drag And Drop API

angular, html, drag, drop

自述文件

npm npm (next) NpmLicense GitHub issues Twitter

NgxDragDrop

Demo / StackBlitz 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
  • custom CSS classes
  • touch support by using a polyfill
  • AOT compatible

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.

Usage

app.component.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

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 {
}

API

// 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>;
}

Touch support

Install the mobile-drag-drop module available on npm.

Add the following lines to your js code

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

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.

更新日志

v13.0.0 (2021-12-30)

Refactored and released for Angular v13.0.0 with some updates in the demos.

v2.0.0 (2019-06-08)

About time to declare it stable after months of rc's.

v2.0.0-rc.7 (2019-06-08)

  • Fixes #67. Thanks @andreroggeri

v2.0.0-rc.6 (2019-04-13)

  • Merges #46 to allow separate disabling of drag/drop when an element is a dropzone that is itself draggable. Thanks @VijaySutariaJCD

v2.0.0-rc.5 (2019-03-01)

  • Updates to Angular v7 (for demo also updates CDK, Material, Flex)
  • adds dndDrag event to dndDraggable
  • adds demo for right hand side dndHandle with correct drag image offset

v2.0.0-rc.4 (2018-10-27)

  • Handle DragEvent.dataTransfer being null for external drag items in Firefox (#35)

v1.1.0 / v2.0.0-rc.3 (2018-09-19)

  • Transport eventually set dndDraggable.dndType as type in DndDropEvent (#42) - thanks to @hordesalik

v2.0.0-rc.2 (2018-09-16)

  • Assert placeholder is still attached before removing from DOM (#37)

v1.0.4 (2018-09-07)

Strip rxjs and zone.js from peerDependencies and allow Angular v6 as peerDependency

v2.0.0-rc.1 (2018-06-28)

  • Changed .remove() => .parentNode.removeChild() for cross-browser compatibility (#27)

Thanks to @gpaucot

v2.0.0-rc.0 (2018-06-05)

Release candidate for next version!

  • Remove rxjs as peer dependency as this lib is not dependent on it.

  • Remove zone.js as peer dependency as this lib is not dependent on it.

  • Adjust angular peer dependency to support Angular v6 without warning.

  • dragover event now bubbles up.

  • Handle various drag events outside of Angular zone to improve performance.

    • I'm not entirely clear if this could be a breaking change to some users so I'm better safe than sorry and raise the major version with this release!

Thanks to @mtraynham for his contributions!

v1.0.3 (2018-01-26)

[dndHandle] and [dndDragImageRef] can now be used when contained in components. Previously it only worked when in the same template as a content child of [dndDraggable].

v1.0.2 (2018-01-12)

  • compatibility with Angular compiler flag fullTemplateTypeCheck introduced in Angular v5.2

v1.0.1 (2017-12-01)

  • correct angular peer dependency to avoid warnings when using angular >4.x.x

v1.0.0 (2017-10-18)

  • stable release!

v1.0.0-rc.3 (2017-09-18)

  • adds demo for nested recursive drag and drop
  • fixes issue where [dndPlaceholderRef] was not working in nested context because of angular limitation

v1.0.0-rc.2 (2017-09-05)

  • adds demo for typed drag and drop
  • fixes issue with typed drag and drop not working

v1.0.0-rc.1 (2017-09-04)

Changes build process to use ng-packagr for less manual AOT compatible library builds.

Demo is updated to use latest dependencies.

v1.0.0-rc.0 (2017-06-29)

Bug Fixes

  • DndDropzone: fixes issue on placeholder positioning algorithm where direct child element was missed

v1.0.0-beta.2 (2017-06-22)

Bug Fixes

  • DndElementRef: access @ContentChild() in ngAfterContentInit to ensure its properly initialized
  • DndModule: re-export DndElementRef in index module

v1.0.0-beta.1 (2017-06-22)

Bug Fixes

  • DndDropzone: correctly handle dragenter to ensure only a single active dropzone on nested dropzones
  • DndDropzone: cleanup dropEffect on dropzone leave to avoid unwanted effects when dropzone was hovered, left and the draggable is then released

Features

  • DndDropzone: specify a placeholder element by using [dndPlaceholderRef] on a child element of [dndDropzone]
  • DndDropzone: DndDropEvent defines property isExternal:boolean to ease handling of external drops
  • DndDraggable: specify a custom drag image by using [dndDragImageRef] on a child element of [dndDraggable]

BREAKING CHANGES

  • DndDropzone: removed input binding [dndPlaceholder] in favor of [dndPlaceholderRef]

BEFORE

<section dndDropzone
         (dndDragover)="dragover = $event"
         (dndDrop)="onDrop($event)"
         [dndPlaceholder]="placeholder">

   dropzone

   <div #placeholder>placeholder
   </div>

</section>

AFTER

<section dndDropzone
         (dndDragover)="dragover = $event"
         (dndDrop)="onDrop($event)">

   dropzone

   <div dndPlaceholderRef>placeholder
   </div>

</section>

This breaking change is needed to provide compatibility with custom elements / components.