@ng-dnd/core
Version:
Drag and Drop for Angular
931 lines (908 loc) • 35.6 kB
JavaScript
import * as i0 from '@angular/core';
import { Directive, Input, InjectionToken, NgModule, NgZone, Injectable, Inject } from '@angular/core';
import { Subscription, ReplaySubject, BehaviorSubject } from 'rxjs';
import { createDragDropManager } from 'dnd-core';
import { take, switchMap, map, distinctUntilChanged, tap } from 'rxjs/operators';
function invariant(assertion, msg) {
if (!assertion) {
throw new Error(msg);
}
}
/** @ignore */
const explanation = 'You can only pass exactly one connection object to [dropTarget]. ' +
'There is only one of each source/target/preview allowed per DOM element.';
/** @ignore */
class AbstractDndDirective {
/** @ignore */
constructor(elRef, ngZone) {
this.elRef = elRef;
this.ngZone = ngZone;
this.deferredRequest = new Subscription();
}
ngOnChanges() {
invariant(typeof this.connection === 'object' && !Array.isArray(this.connection), explanation);
this.ngZone.runOutsideAngular(() => {
// discard an unresolved connection request
// in the case where the previous one succeeded, deferredRequest is
// already closed.
this.deferredRequest.unsubscribe();
// replace it with a new one
if (this.connection) {
this.deferredRequest = this.callHooks(this.connection);
}
});
}
ngOnDestroy() {
this.deferredRequest.unsubscribe();
}
callHooks(_conn) {
return new Subscription();
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: AbstractDndDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.5", type: AbstractDndDirective, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: AbstractDndDirective, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }] });
/**
* Allows you to connect a {@link DropTarget} to an element in a component template.
*/
class DropTargetDirective extends AbstractDndDirective {
/** Reduce typo confusion by allowing non-plural version of dropTargetTypes */
set dropTargetType(t) {
this.dropTargetTypes = t;
}
ngOnChanges() {
this.connection = this.dropTarget;
if (this.connection && this.dropTargetTypes != null) {
this.connection.setTypes(this.dropTargetTypes);
}
super.ngOnChanges();
}
callHooks(conn) {
return conn.connectDropTarget(this.elRef.nativeElement);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DropTargetDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.5", type: DropTargetDirective, isStandalone: true, selector: "[dropTarget]", inputs: { dropTarget: "dropTarget", dropTargetTypes: "dropTargetTypes", dropTargetType: "dropTargetType" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DropTargetDirective, decorators: [{
type: Directive,
args: [{
selector: '[dropTarget]',
standalone: true,
}]
}], propDecorators: { dropTarget: [{
type: Input,
args: ['dropTarget']
}], dropTargetTypes: [{
type: Input,
args: ['dropTargetTypes']
}], dropTargetType: [{
type: Input,
args: ['dropTargetType']
}] } });
/** Allows you to connect a {@link DragSource} to an element in a component template. */
class DragSourceDirective extends AbstractDndDirective {
constructor() {
super(...arguments);
/**
* Do not render an HTML5 preview. Only applies when using the HTML5 backend.
* It does not use { captureDraggingState: true } for IE11 support; that is broken.
*/
this.noHTML5Preview = false;
}
ngOnChanges() {
this.connection = this.dragSource;
if (this.connection && this.dragSourceType != null) {
this.connection.setType(this.dragSourceType);
}
super.ngOnChanges();
}
callHooks(conn) {
const sub = new Subscription();
sub.add(conn.connectDragSource(this.elRef.nativeElement, this.dragSourceOptions));
if (this.noHTML5Preview) {
sub.add(conn.connectDragPreview(getEmptyImage()));
}
return sub;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DragSourceDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.5", type: DragSourceDirective, isStandalone: true, selector: "[dragSource]", inputs: { dragSource: "dragSource", dragSourceType: "dragSourceType", dragSourceOptions: "dragSourceOptions", noHTML5Preview: "noHTML5Preview" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DragSourceDirective, decorators: [{
type: Directive,
args: [{
selector: '[dragSource]',
standalone: true,
}]
}], propDecorators: { dragSource: [{
type: Input,
args: ['dragSource']
}], dragSourceType: [{
type: Input,
args: ['dragSourceType']
}], dragSourceOptions: [{
type: Input,
args: ['dragSourceOptions']
}], noHTML5Preview: [{
type: Input,
args: ['noHTML5Preview']
}] } });
/**
* Allows you to specify which element a {@link DragSource} should screenshot
* as an HTML5 drag preview.
*
* Only relevant when using the HTML5 backend.
*/
class DragPreviewDirective extends AbstractDndDirective {
ngOnChanges() {
this.connection = this.dragPreview;
super.ngOnChanges();
}
callHooks(conn) {
return conn.connectDragPreview(this.elRef.nativeElement, this.dragPreviewOptions);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DragPreviewDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.5", type: DragPreviewDirective, isStandalone: true, selector: "[dragPreview]", inputs: { dragPreview: "dragPreview", dragPreviewOptions: "dragPreviewOptions" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DragPreviewDirective, decorators: [{
type: Directive,
args: [{
selector: '[dragPreview]',
standalone: true,
}]
}], propDecorators: { dragPreview: [{
type: Input,
args: ['dragPreview']
}], dragPreviewOptions: [{
type: Input,
args: ['dragPreviewOptions']
}] } });
// import { getEmptyImage } from 'react-dnd-html5-backend';
// we don't want to depend on the backend, so here that is, copied
/** @ignore */
let emptyImage;
/**
* Returns a 0x0 empty GIF for use as a drag preview.
* @ignore
*/
function getEmptyImage() {
if (!emptyImage) {
emptyImage = new Image();
emptyImage.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
}
return emptyImage;
}
/**
* @module Misc
*/
/** The injection token for the dnd-core compatible backend currently in use. */
const DRAG_DROP_BACKEND = new InjectionToken('dnd-core compatible backend');
/** The injection token for the dnd-core BackendFactory used to instantiate dnd-core. */
const DRAG_DROP_BACKEND_FACTORY = new InjectionToken('dnd-core compatible backend');
/** The injection token for the dnd-core compatible backend's options. */
const DRAG_DROP_BACKEND_OPTIONS = new InjectionToken('options for dnd-core compatible backend');
/** The injection token for the dnd-core compatible backend currently in use. */
const DRAG_DROP_BACKEND_DEBUG_MODE = new InjectionToken('should dnd-core run in debug mode?');
/** The injection token for the dnd-core DragDropManager */
const DRAG_DROP_MANAGER = new InjectionToken('dnd-core DragDropManager');
/** The injection token for the dnd-core compatible backend currently in use. */
const DRAG_DROP_GLOBAL_CONTEXT = new InjectionToken('dnd-core context');
/**
* The type a source or target is given as a marker for 'you supplied null as a type',
* so that library consumers can be reminded to use setType/setTypes manually.
* See {@link DragSource#setType}, {@link DropTarget#setTypes}.
*/
const TYPE_DYNAMIC = Symbol('no type specified, you must provide one with setType/setTypes');
/** @ignore */
function unpackBackendForEs5Users(backendOrModule) {
// Auto-detect ES6 default export for people still using ES5
let backend = backendOrModule;
if (typeof backend === 'object' && typeof backend.default === 'function') {
backend = backend.default;
}
invariant(typeof backend === 'function', 'Expected the backend to be a function or an ES6 module exporting a default function. ' +
'Read more: http://react-dnd.github.io/react-dnd/docs-drag-drop-context.html');
return backend;
}
// TODO: allow injecting window
/** @ignore */
// @dynamic
function managerFactory(backendFactory, ngZone, context, backendOptions, debugMode) {
backendFactory = unpackBackendForEs5Users(backendFactory);
return ngZone.runOutsideAngular(() => createDragDropManager(backendFactory, context, backendOptions, debugMode));
}
/** @ignore */
// @dynamic
function getBackend(manager) {
return manager.getBackend();
}
/** @ignore */
function getGlobalContext() {
return typeof global !== 'undefined' ? global : window;
}
/** @ignore */
const EXPORTS = [DragSourceDirective, DropTargetDirective, DragPreviewDirective];
// @dynamic
class DndModule {
static forRoot(backendInput) {
return {
ngModule: DndModule,
providers: [provideDnd(backendInput)],
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.5", ngImport: i0, type: DndModule, imports: [DragSourceDirective, DropTargetDirective, DragPreviewDirective], exports: [DragSourceDirective, DropTargetDirective, DragPreviewDirective] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndModule, decorators: [{
type: NgModule,
args: [{
imports: EXPORTS,
exports: EXPORTS,
}]
}] });
function provideDnd(backendInput) {
return [
{
provide: DRAG_DROP_BACKEND_FACTORY,
useValue: backendInput.backend,
},
{
provide: DRAG_DROP_BACKEND_OPTIONS,
useValue: backendInput.options,
},
{
provide: DRAG_DROP_BACKEND_DEBUG_MODE,
useValue: backendInput.debug,
},
{
provide: DRAG_DROP_GLOBAL_CONTEXT,
useFactory: getGlobalContext,
},
{
provide: DRAG_DROP_MANAGER,
useFactory: managerFactory,
deps: [
DRAG_DROP_BACKEND_FACTORY,
NgZone,
DRAG_DROP_GLOBAL_CONTEXT,
DRAG_DROP_BACKEND_OPTIONS,
DRAG_DROP_BACKEND_DEBUG_MODE,
],
},
{
provide: DRAG_DROP_BACKEND,
deps: [DRAG_DROP_MANAGER],
useFactory: getBackend,
},
];
}
function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
const hasOwn = Object.prototype.hasOwnProperty;
for (let i = 0; i < keysA.length; i += 1) {
if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
const valA = objA[keysA[i]];
const valB = objB[keysA[i]];
if (valA !== valB) {
return false;
}
}
return true;
}
function areOptionsEqual(nextOptions, currentOptions) {
if (currentOptions === nextOptions) {
return true;
}
return (currentOptions !== null && nextOptions !== null && shallowEqual(currentOptions, nextOptions));
}
class Reconnector {
constructor(backendConnector) {
this.backendConnector = backendConnector;
this.reconnect = (parentHandlerId) => {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
this.handlerId = parentHandlerId;
if (this.handlerId && this.node) {
this.disconnect = this.backendConnector(this.handlerId, this.node, this.options);
}
};
this.hook = (nativeElement, options) => {
if (nativeElement === this.node && areOptionsEqual(options, this.options)) {
return;
}
this.node = nativeElement;
this.options = options;
this.reconnect(this.handlerId);
};
}
}
class TargetConnector {
constructor(backend) {
this.backend = backend;
this.dropTarget = new Reconnector((handlerId, node, options) => {
return this.backend.connectDropTarget(handlerId, node, options);
});
this.hooks = {
dropTarget: this.dropTarget.hook,
};
}
receiveHandlerId(handlerId) {
if (handlerId === this.currentHandlerId) {
return;
}
this.currentHandlerId = handlerId;
this.dropTarget.reconnect(handlerId);
}
reconnect() {
this.dropTarget.reconnect(this.currentHandlerId);
}
}
function createTargetConnector(backend) {
return new TargetConnector(backend);
}
function registerTarget(type, target, manager) {
const registry = manager.getRegistry();
const targetId = registry.addTarget(type, target);
function unregisterTarget() {
registry.removeTarget(targetId);
}
return {
handlerId: targetId,
unregister: unregisterTarget,
};
}
class SourceConnector {
constructor(backend) {
this.backend = backend;
this.dragSource = new Reconnector((handlerId, node, options) => {
return this.backend.connectDragSource(handlerId, node, options);
});
this.dragPreview = new Reconnector((handlerId, node, options) => {
return this.backend.connectDragPreview(handlerId, node, options);
});
this.hooks = {
dragSource: this.dragSource.hook,
dragPreview: this.dragPreview.hook,
};
}
receiveHandlerId(handlerId) {
if (handlerId === this.currentHandlerId) {
return;
}
this.currentHandlerId = handlerId;
this.dragSource.reconnect(handlerId);
this.dragPreview.reconnect(handlerId);
}
reconnect() {
this.dragSource.reconnect(this.currentHandlerId);
this.dragPreview.reconnect(this.currentHandlerId);
}
}
function createSourceConnector(backend) {
return new SourceConnector(backend);
}
function registerSource(type, source, manager) {
const registry = manager.getRegistry();
const sourceId = registry.addSource(type, source);
function unregisterSource() {
registry.removeSource(sourceId);
}
return {
handlerId: sourceId,
unregister: unregisterSource,
};
}
function areCollectsEqual(a, b) {
if (a == null || b == null) {
return false;
}
if (typeof a !== 'object' || typeof b !== 'object') {
return a === b;
}
return shallowEqual(a, b);
}
class Connection {
constructor(factoryArgs, manager, ngZone, initialType) {
this.factoryArgs = factoryArgs;
this.manager = manager;
this.ngZone = ngZone;
/** A subject basically used to kick off any observables waiting for a type to be set via setType/setTypes */
this.resolvedType$ = new ReplaySubject(1);
/**
* This one lives exactly as long as the connection.
* It is responsible for disposing of the handlerConnector, and any internal listen() subscriptions.
*/
this.subscriptionConnectionLifetime = new Subscription();
this.onUpdate = () => {
this.handlerConnector.reconnect();
};
this.handleChange = () => {
this.collector$.next(this.handlerMonitor);
};
invariant(typeof manager === 'object',
// TODO: update this mini-documentation
'Could not find the drag and drop manager in the context of %s. ' +
'Make sure to wrap the top-level component of your app with DragDropContext. '
// 'Read more: ',
);
NgZone.assertNotInAngularZone();
this.handlerMonitor = this.factoryArgs.createMonitor(this.manager);
this.collector$ = new BehaviorSubject(this.handlerMonitor);
this.handler = this.factoryArgs.createHandler(this.handlerMonitor);
this.handlerConnector = this.factoryArgs.createConnector(this.manager.getBackend());
// handlerConnector lives longer than any per-type subscription
this.subscriptionConnectionLifetime.add(() => this.handlerConnector.receiveHandlerId(null));
if (initialType && initialType !== TYPE_DYNAMIC) {
this.setTypes(initialType);
}
}
listen(mapFn) {
// Listeners are generally around as long as the connection.
// This isn't 100% true, but there is no way of knowing (even if you ref-count it)
// when a component no longer needs it.
return this.resolvedType$.pipe(
// this ensures we don't start emitting values until there is a type resolved
take(1),
// switch our attention to the incoming firehose of 'something changed' events
switchMap(() => this.collector$),
// turn them into 'interesting state' via the monitor and a user-provided function
map(mapFn),
// don't emit EVERY time the firehose says something changed, only when the interesting state changes
distinctUntilChanged(areCollectsEqual),
// TODO: how to reduce the frequency of change detection?
tap(this.onUpdate));
}
connect(fn) {
const subscription = this.resolvedType$.pipe(take(1)).subscribe(() => {
// must run inside ngZone otherwise the zone app may have small issue
this.ngZone.run(() => {
fn(this.handlerConnector.hooks);
});
});
// now chain this onto the connection's unsubscribe call.
// just in case you destroy your component before setting a type on anything
// i.e.:
// conn without a type
// source = this.dnd.dragSource(null, { ... })
// manually connect to the DOM, which won't handle the returned subscription like the directive does
// ngAfterViewInit() { this.source.connectDragSource(this.myDiv.nativeElement); }
// never set a type
// then destroy your component, the source, but not the connection request.
// ngOnDestroy() { this.source.unsubscribe(); }
//
// without this, you would have a hanging resolvedType$.pipe(take(1)) subscription
// with this, it dies with the source's unsubscribe call.
//
// doesn't need this.subscriptionTypeLifetime, because pipe(take(1)) already does that
this.subscriptionConnectionLifetime.add(subscription);
return subscription;
}
connectDropTarget(node) {
return this.connect(c => c.dropTarget(node));
}
connectDragSource(node, options) {
return this.connect(c => c.dragSource(node, options));
}
connectDragPreview(node, options) {
return this.connect(c => c.dragPreview(node, options));
}
setTypes(type) {
// must run outside ngZone
this.ngZone.runOutsideAngular(() => {
this.receiveType(type);
this.resolvedType$.next(1);
});
}
setType(type) {
this.setTypes(type);
}
getHandlerId() {
return this.handlerId;
}
receiveType(type) {
if (type === this.currentType) {
return;
}
NgZone.assertNotInAngularZone();
this.currentType = type;
if (this.subscriptionTypeLifetime) {
this.subscriptionTypeLifetime.unsubscribe();
}
// console.debug('subscribed to ' + type.toString());
this.subscriptionTypeLifetime = new Subscription();
const { handlerId, unregister } = this.factoryArgs.registerHandler(type, this.handler, this.manager);
this.handlerId = handlerId;
this.handlerMonitor.receiveHandlerId(handlerId);
this.handlerConnector.receiveHandlerId(handlerId);
const globalMonitor = this.manager.getMonitor();
const unsubscribe = globalMonitor.subscribeToStateChange(this.handleChange, {
handlerIds: [handlerId],
});
this.subscriptionTypeLifetime.add(unsubscribe);
this.subscriptionTypeLifetime.add(unregister);
// this.subscriptionTypeLifetime.add(() => console.debug("unsubscribed from " + type.toString()));
}
unsubscribe() {
if (this.subscriptionTypeLifetime) {
this.subscriptionTypeLifetime.unsubscribe();
}
this.subscriptionConnectionLifetime.unsubscribe();
}
add(teardown) {
return this.subscriptionConnectionLifetime.add(teardown);
}
get closed() {
return this.subscriptionConnectionLifetime && this.subscriptionConnectionLifetime.closed;
}
}
const TargetConnection = Connection;
const SourceConnection = Connection;
class DragLayerConnectionClass {
constructor(manager) {
this.manager = manager;
this.subscription = new Subscription();
this.isTicking = false;
this.handleStateChange = () => {
const monitor = this.manager.getMonitor();
this.collector$.next(monitor);
};
this.handleOffsetChange = () => {
const monitor = this.manager.getMonitor();
this.collector$.next(monitor);
};
const monitor = this.manager.getMonitor();
this.collector$ = new BehaviorSubject(monitor);
this.unsubscribeFromOffsetChange = monitor.subscribeToOffsetChange(this.handleOffsetChange);
this.unsubscribeFromStateChange = monitor.subscribeToStateChange(this.handleStateChange);
this.subscription.add(() => {
this.unsubscribeFromOffsetChange();
this.unsubscribeFromStateChange();
});
this.handleStateChange();
}
listen(mapFn) {
return this.collector$.pipe(map(mapFn), distinctUntilChanged(areCollectsEqual));
}
unsubscribe() {
this.collector$.complete();
this.subscription.unsubscribe();
}
add(teardown) {
return this.subscription.add(teardown);
}
get closed() {
return this.subscription.closed;
}
}
class Source {
constructor(spec, monitor) {
this.spec = spec;
this.monitor = monitor;
}
withChangeDetection(fn) {
const x = fn();
return x;
}
canDrag() {
if (!this.spec.canDrag) {
return true;
}
return this.withChangeDetection(() => {
return this.spec.canDrag?.(this.monitor) || false;
});
}
isDragging(globalMonitor, sourceId) {
if (!this.spec.isDragging) {
return sourceId === globalMonitor.getSourceId();
}
return this.spec.isDragging(this.monitor);
}
beginDrag() {
return this.withChangeDetection(() => {
return this.spec.beginDrag(this.monitor);
});
}
endDrag() {
if (!this.spec.endDrag) {
return;
}
return this.withChangeDetection(() => {
this.spec.endDrag?.(this.monitor);
});
}
}
function createSourceFactory(spec) {
return function createSource(monitor) {
return new Source(spec, monitor);
};
}
let isCallingCanDrag = false;
let isCallingIsDragging = false;
class DragSourceMonitorClass {
constructor(manager) {
this.internalMonitor = manager.getMonitor();
}
receiveHandlerId(sourceId) {
this.sourceId = sourceId;
}
canDrag() {
invariant(!isCallingCanDrag, 'You may not call monitor.canDrag() inside your canDrag() implementation. ' +
'Read more: http://react-dnd.github.io/react-dnd/docs-drag-source-monitor.html');
try {
isCallingCanDrag = true;
return this.internalMonitor.canDragSource(this.sourceId);
}
finally {
isCallingCanDrag = false;
}
}
isDragging() {
invariant(!isCallingIsDragging, 'You may not call monitor.isDragging() inside your isDragging() implementation. ' +
'Read more: http://react-dnd.github.io/react-dnd/docs-drag-source-monitor.html');
try {
isCallingIsDragging = true;
return this.internalMonitor.isDraggingSource(this.sourceId);
}
finally {
isCallingIsDragging = false;
}
}
getItemType() {
return this.internalMonitor.getItemType();
}
getItem() {
return this.internalMonitor.getItem();
}
getDropResult() {
return this.internalMonitor.getDropResult();
}
didDrop() {
return this.internalMonitor.didDrop();
}
getInitialClientOffset() {
return this.internalMonitor.getInitialClientOffset();
}
getInitialSourceClientOffset() {
return this.internalMonitor.getInitialSourceClientOffset();
}
getSourceClientOffset() {
return this.internalMonitor.getSourceClientOffset();
}
getClientOffset() {
return this.internalMonitor.getClientOffset();
}
getDifferenceFromInitialOffset() {
return this.internalMonitor.getDifferenceFromInitialOffset();
}
}
function createSourceMonitor(manager) {
return new DragSourceMonitorClass(manager);
}
class Target {
constructor(spec, monitor) {
this.spec = spec;
this.monitor = monitor;
this.monitor = monitor;
}
withChangeDetection(fn) {
const x = fn();
return x;
}
receiveMonitor(monitor) {
this.monitor = monitor;
}
canDrop() {
if (!this.spec.canDrop) {
return true;
}
// Don't run isDragging in the zone. Should be a pure function of `this`.
return this.spec.canDrop(this.monitor);
}
hover() {
if (!this.spec.hover) {
return;
}
this.withChangeDetection(() => {
this.spec.hover?.(this.monitor);
});
}
drop() {
if (!this.spec.drop) {
return undefined;
}
return this.withChangeDetection(() => {
return this.spec.drop?.(this.monitor);
});
}
}
function createTargetFactory(spec) {
return function createTarget(monitor) {
return new Target(spec, monitor);
};
}
let isCallingCanDrop = false;
class DropTargetMonitorClass {
constructor(manager) {
this.internalMonitor = manager.getMonitor();
}
receiveHandlerId(targetId) {
this.targetId = targetId;
}
canDrop() {
invariant(!isCallingCanDrop, 'You may not call monitor.canDrop() inside your canDrop() implementation. ' +
'Read more: http://react-dnd.github.io/react-dnd/docs-drop-target-monitor.html');
try {
isCallingCanDrop = true;
return this.internalMonitor.canDropOnTarget(this.targetId);
}
finally {
isCallingCanDrop = false;
}
}
isOver(options = { shallow: false }) {
return this.internalMonitor.isOverTarget(this.targetId, options);
}
getItemType() {
return this.internalMonitor.getItemType();
}
getItem() {
return this.internalMonitor.getItem();
}
getDropResult() {
return this.internalMonitor.getDropResult();
}
didDrop() {
return this.internalMonitor.didDrop();
}
getInitialClientOffset() {
return this.internalMonitor.getInitialClientOffset();
}
getInitialSourceClientOffset() {
return this.internalMonitor.getInitialSourceClientOffset();
}
getSourceClientOffset() {
return this.internalMonitor.getSourceClientOffset();
}
getClientOffset() {
return this.internalMonitor.getClientOffset();
}
getDifferenceFromInitialOffset() {
return this.internalMonitor.getDifferenceFromInitialOffset();
}
}
function createTargetMonitor(manager) {
return new DropTargetMonitorClass(manager);
}
/**
* @module 1-Top-Level
*/
/** a second comment */
/**
* For a simple component, unsubscribing is as easy as `connection.unsubscribe()` in `ngOnDestroy()`
* If your components have lots of subscriptions, it can get tedious having to
* unsubscribe from all of them, and you might forget. A common pattern is to create an RxJS Subscription
* (maybe called `destroy`), to use `this.destroy.add(xxx.subscribe(...))`
* and to call `destroy.unsubscribe()` once to clean up all of them. @ng-dnd/core
* supports this pattern with by using the `subscription` parameter on the
* constructors. Simply:
*
* ```typescript
* import { Subscription } from 'rxjs';
* // ...
* destroy = new Subscription();
* target = this.dnd.dropTarget({
* // ...
* }, this.destroy);
* ngOnDestroy() { this.destroy.unsubscribe(); }
* ```
*
* It is a good habit for avoiding leaked subscriptions, because .
*/
class DndService {
/** @ignore */
constructor(manager, ngZone) {
this.manager = manager;
this.ngZone = ngZone;
}
/**
* This drop target will only react to the items produced by the drag sources
* of the specified type or types.
*
* If you want a dynamic type, pass `null` as the type; and call
* {@link DropTarget#setTypes} in a lifecycle hook.
*/
dropTarget(types, spec, subscription) {
return this.ngZone.runOutsideAngular(() => {
const createTarget = createTargetFactory(spec);
const conn = new TargetConnection({
createHandler: createTarget,
registerHandler: registerTarget,
createMonitor: createTargetMonitor,
createConnector: createTargetConnector,
}, this.manager, this.ngZone, types || TYPE_DYNAMIC);
if (subscription) {
subscription.add(conn);
}
return conn;
});
}
/**
* This method creates a {@link DragSource} object. It represents a drag
* source and its behaviour, and can be connected to a DOM element by
* assigning it to the `[dragSource]` directive on that element in your
* template.
*
* It is the corollary of [`react-dnd`'s
* `DragSource`](http://react-dnd.github.io/react-dnd/docs-drag-source.html).
*
* The `spec` argument ({@link DragSourceSpec}) is a set of _queries_ and
* _callbacks_ that are called at appropriate times by the internals. The
* queries are for asking your component whether to drag/listen and what
* item data to hoist up; the callback (just 1) is for notifying you when
* the drag ends.
*
* Only the drop targets registered for the same type will
* react to the items produced by this drag source. If you want a dynamic
* type, pass `null` as the type; and call {@link DragSource#setType} in
* a lifecycle hook.
*
* @param subscription An RxJS Subscription to tie the lifetime of the
* connection to.
*/
dragSource(type, spec, subscription) {
return this.ngZone.runOutsideAngular(() => {
const createSource = createSourceFactory(spec);
const conn = new SourceConnection({
createHandler: createSource,
registerHandler: registerSource,
createMonitor: createSourceMonitor,
createConnector: createSourceConnector,
}, this.manager, this.ngZone, type || TYPE_DYNAMIC);
if (subscription) {
subscription.add(conn);
}
return conn;
});
}
/**
* This method creates a {@link DragLayer} object
*/
dragLayer(subscription) {
return this.ngZone.runOutsideAngular(() => {
const conn = new DragLayerConnectionClass(this.manager);
if (subscription) {
subscription.add(conn);
}
return conn;
});
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndService, deps: [{ token: DRAG_DROP_MANAGER }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.5", ngImport: i0, type: DndService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [DRAG_DROP_MANAGER]
}] }, { type: i0.NgZone }] });
// import no symbols to get typings but not execute the monkey-patching module loader
/**
* Generated bundle index. Do not edit.
*/
export { AbstractDndDirective, DRAG_DROP_BACKEND, DRAG_DROP_MANAGER, DndModule, DndService, DragPreviewDirective, DragSourceDirective, DropTargetDirective, getBackend, getGlobalContext, managerFactory, provideDnd, unpackBackendForEs5Users };
//# sourceMappingURL=ng-dnd-core.mjs.map