UNPKG

@ng-dnd/core

Version:

Drag and Drop for Angular

931 lines (908 loc) 35.6 kB
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