feat(drag-drop): add service for attaching drag&drop to arbitrary DOM nodes (#14437)

* Adds the  `DragDrop` service that simplifies the construction logic for `DragRef` and `DropListRef` and allows consumers to attach drag&drop functionality to arbitrary DOM nodes, rather than having to go through the directives.
* Reworks `DragRef` and `DragDropRef` to make them easier to construct.
* Normalizes some of the instances where some parameters only accept an `ElementRef`, whereas others accept `ElementRef | HTMLElement`.
This commit is contained in:
Kristiyan Kostadinov
2019-01-25 18:07:15 +01:00
committed by Andrew Seguin
parent e66131744a
commit 5a18ea61e5
11 changed files with 423 additions and 97 deletions
+1
View File
@@ -23,6 +23,7 @@ ng_test_library(
name = "drag-drop_test_sources",
srcs = glob(["**/*.spec.ts"]),
deps = [
"@rxjs",
"//src/cdk/testing",
"//src/cdk/bidi",
":drag-drop",
+3 -2
View File
@@ -21,6 +21,7 @@ import {
createTouchEvent,
} from '@angular/cdk/testing';
import {Directionality} from '@angular/cdk/bidi';
import {of as observableOf} from 'rxjs';
import {CdkDrag, CDK_DRAG_CONFIG} from './drag';
import {CdkDragDrop} from '../drag-events';
import {moveItemInArray} from '../drag-utils';
@@ -1140,7 +1141,7 @@ describe('CdkDrag', () => {
it('should dispatch the correct `dropped` event in RTL horizontal drop zone', fakeAsync(() => {
const fixture = createComponent(DraggableInHorizontalDropZone, [{
provide: Directionality,
useValue: ({value: 'rtl'})
useValue: ({value: 'rtl', change: observableOf()})
}]);
fixture.nativeElement.setAttribute('dir', 'rtl');
@@ -1296,7 +1297,7 @@ describe('CdkDrag', () => {
it('should pass the proper direction to the preview in rtl', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone, [{
provide: Directionality,
useValue: ({value: 'rtl'})
useValue: ({value: 'rtl', change: observableOf()})
}]);
fixture.detectChanges();
+43 -17
View File
@@ -50,6 +50,7 @@ import {CDK_DRAG_PARENT} from '../drag-parent';
import {DragRef, DragRefConfig} from '../drag-ref';
import {DropListRef} from '../drop-list-ref';
import {CdkDropListInternal as CdkDropList} from './drop-list';
import {DragDrop} from '../drag-drop';
/** Injection token that can be used to configure the behavior of `CdkDrag`. */
export const CDK_DRAG_CONFIG = new InjectionToken<DragRefConfig>('CDK_DRAG_CONFIG', {
@@ -167,18 +168,30 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
@Inject(DOCUMENT) private _document: any,
private _ngZone: NgZone,
private _viewContainerRef: ViewContainerRef,
private _viewportRuler: ViewportRuler,
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
@Inject(CDK_DRAG_CONFIG) private _config: DragRefConfig,
@Optional() private _dir: Directionality) {
viewportRuler: ViewportRuler,
dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
@Inject(CDK_DRAG_CONFIG) config: DragRefConfig,
@Optional() private _dir: Directionality,
const ref = this._dragRef = new DragRef(element, this._document, this._ngZone,
this._viewContainerRef, this._viewportRuler, this._dragDropRegistry,
this._config, this.dropContainer ? this.dropContainer._dropListRef : undefined,
this._dir);
ref.data = this;
this._syncInputs(ref);
this._proxyEvents(ref);
/**
* @deprecated `viewportRuler` and `dragDropRegistry` parameters
* to be removed. Also `dragDrop` parameter to be made required.
* @breaking-change 8.0.0.
*/
dragDrop?: DragDrop) {
// @breaking-change 8.0.0 Remove null check once the paramter is made required.
if (dragDrop) {
this._dragRef = dragDrop.createDrag(element, config);
} else {
this._dragRef = new DragRef(element, config, _document, _ngZone, viewportRuler,
dragDropRegistry);
}
this._dragRef.data = this;
this._syncInputs(this._dragRef);
this._proxyEvents(this._dragRef);
}
/**
@@ -273,15 +286,28 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
private _syncInputs(ref: DragRef<CdkDrag<T>>) {
ref.beforeStarted.subscribe(() => {
if (!ref.isDragging()) {
const {_placeholderTemplate: placeholder, _previewTemplate: preview} = this;
const dir = this._dir;
const placeholder = this._placeholderTemplate ? {
template: this._placeholderTemplate.templateRef,
context: this._placeholderTemplate.data,
viewContainer: this._viewContainerRef
} : null;
const preview = this._previewTemplate ? {
template: this._previewTemplate.templateRef,
context: this._previewTemplate.data,
viewContainer: this._viewContainerRef
} : null;
ref.disabled = this.disabled;
ref.lockAxis = this.lockAxis;
ref.withBoundaryElement(this._getBoundaryElement());
placeholder ? ref.withPlaceholderTemplate(placeholder.templateRef, placeholder.data) :
ref.withPlaceholderTemplate(null);
preview ? ref.withPreviewTemplate(preview.templateRef, preview.data) :
ref.withPreviewTemplate(null);
ref
.withBoundaryElement(this._getBoundaryElement())
.withPlaceholderTemplate(placeholder)
.withPreviewTemplate(preview);
if (dir) {
ref.withDirection(dir.value);
}
}
});
}
+53 -15
View File
@@ -21,6 +21,7 @@ import {
ChangeDetectorRef,
SkipSelf,
Inject,
AfterContentInit,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {Directionality} from '@angular/cdk/bidi';
@@ -31,6 +32,9 @@ import {CDK_DROP_LIST_CONTAINER, CdkDropListContainer} from '../drop-list-contai
import {CdkDropListGroup} from './drop-list-group';
import {DropListRef} from '../drop-list-ref';
import {DragRef} from '../drag-ref';
import {DragDrop} from '../drag-drop';
import {Subject} from 'rxjs';
import {startWith, takeUntil} from 'rxjs/operators';
/** Counter used to generate unique ids for drop zones. */
let _uniqueIdCounter = 0;
@@ -62,7 +66,10 @@ export interface CdkDropListInternal extends CdkDropList {}
'[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()',
}
})
export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentInit, OnDestroy {
/** Emits when the list has been destroyed. */
private _destroyed = new Subject<void>();
/** Keeps track of the drop lists that are currently on the page. */
private static _dropLists: CdkDropList[] = [];
@@ -70,7 +77,11 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
_dropListRef: DropListRef<CdkDropList<T>>;
/** Draggable items in the container. */
@ContentChildren(forwardRef(() => CdkDrag)) _draggables: QueryList<CdkDrag>;
@ContentChildren(forwardRef(() => CdkDrag), {
// Explicitly set to false since some of the logic below makes assumptions about it.
// The `.withItems` call below should be updated if we ever need to switch this to `true`.
descendants: false
}) _draggables: QueryList<CdkDrag>;
/**
* Other draggable containers that this container is connected to and into which the
@@ -134,24 +145,35 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
sorted: EventEmitter<CdkDragSortEvent<T>> = new EventEmitter<CdkDragSortEvent<T>>();
constructor(
/** Element that the drop list is attached to. */
public element: ElementRef<HTMLElement>,
dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() dir?: Directionality,
@Optional() private _dir?: Directionality,
@Optional() @SkipSelf() private _group?: CdkDropListGroup<CdkDropList>,
// @breaking-change 8.0.0 `_document` parameter to be made required.
@Optional() @Inject(DOCUMENT) _document?: any) {
@Optional() @Inject(DOCUMENT) _document?: any,
/**
* @deprecated `dragDropRegistry` and `_document` parameters to be removed.
* Also `dragDrop` parameter to be made required.
* @breaking-change 8.0.0.
*/
dragDrop?: DragDrop) {
// @breaking-change 8.0.0 Remove || once `_document` parameter is required.
const ref = this._dropListRef = new DropListRef(element, dragDropRegistry,
_document || document, dir);
ref.data = this;
ref.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
// @breaking-change 8.0.0 Remove null check once `dragDrop` parameter is made required.
if (dragDrop) {
this._dropListRef = dragDrop.createDropList(element);
} else {
this._dropListRef = new DropListRef(element, dragDropRegistry, _document || document);
}
this._dropListRef.data = this;
this._dropListRef.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
return this.enterPredicate(drag.data, drop.data);
};
this._syncInputs(ref);
this._handleEvents(ref);
this._syncInputs(this._dropListRef);
this._handleEvents(this._dropListRef);
CdkDropList._dropLists.push(this);
if (_group) {
@@ -159,9 +181,16 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
}
}
ngAfterContentInit() {
this._draggables.changes
.pipe(startWith(this._draggables), takeUntil(this._destroyed))
.subscribe((items: QueryList<CdkDrag>) => {
this._dropListRef.withItems(items.map(drag => drag._dragRef));
});
}
ngOnDestroy() {
const index = CdkDropList._dropLists.indexOf(this);
this._dropListRef.dispose();
if (index > -1) {
CdkDropList._dropLists.splice(index, 1);
@@ -170,6 +199,10 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
if (this._group) {
this._group._items.delete(this);
}
this._dropListRef.dispose();
this._destroyed.next();
this._destroyed.complete();
}
/** Starts dragging an item. */
@@ -253,6 +286,12 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
/** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */
private _syncInputs(ref: DropListRef<CdkDropList>) {
if (this._dir) {
this._dir.change
.pipe(startWith(this._dir.value), takeUntil(this._destroyed))
.subscribe(value => ref.withDirection(value));
}
ref.beforeStarted.subscribe(() => {
const siblings = coerceArray(this.connectedTo).map(drop => {
return typeof drop === 'string' ?
@@ -270,8 +309,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
ref.lockAxis = this.lockAxis;
ref
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
.withOrientation(this.orientation)
.withItems(this._draggables.map(drag => drag._dragRef));
.withOrientation(this.orientation);
});
}
+4
View File
@@ -13,6 +13,7 @@ import {CdkDrag} from './directives/drag';
import {CdkDragHandle} from './directives/drag-handle';
import {CdkDragPreview} from './directives/drag-preview';
import {CdkDragPlaceholder} from './directives/drag-placeholder';
import {DragDrop} from './drag-drop';
@NgModule({
declarations: [
@@ -31,5 +32,8 @@ import {CdkDragPlaceholder} from './directives/drag-placeholder';
CdkDragPreview,
CdkDragPlaceholder,
],
providers: [
DragDrop,
]
})
export class DragDropModule {}
+47
View File
@@ -0,0 +1,47 @@
import {Component, ElementRef} from '@angular/core';
import {fakeAsync, TestBed, inject} from '@angular/core/testing';
import {DragDropModule} from './drag-drop-module';
import {DragDrop} from './drag-drop';
import {DragRef} from './drag-ref';
import {DropListRef} from './drop-list-ref';
describe('DragDrop', () => {
let service: DragDrop;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [DragDropModule],
});
TestBed.compileComponents();
}));
beforeEach(inject([DragDrop], (d: DragDrop) => {
service = d;
}));
it('should be able to attach a DragRef to a DOM node', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const ref = service.createDrag(fixture.componentInstance.elementRef);
expect(ref instanceof DragRef).toBe(true);
});
it('should be able to attach a DropListRef to a DOM node', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const ref = service.createDropList(fixture.componentInstance.elementRef);
expect(ref instanceof DropListRef).toBe(true);
});
});
@Component({
template: '<div></div>'
})
class TestComponent {
constructor(public elementRef: ElementRef<HTMLElement>) {}
}
+52
View File
@@ -0,0 +1,52 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, Inject, NgZone, ElementRef} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {DragRef, DragRefConfig} from './drag-ref';
import {DropListRef} from './drop-list-ref';
import {DragDropRegistry} from './drag-drop-registry';
/** Default configuration to be used when creating a `DragRef`. */
const DEFAULT_CONFIG = {
dragStartThreshold: 5,
pointerDirectionChangeThreshold: 5
};
/**
* Service that allows for drag-and-drop functionality to be attached to DOM elements.
*/
@Injectable({providedIn: 'root'})
export class DragDrop {
constructor(
@Inject(DOCUMENT) private _document: any,
private _ngZone: NgZone,
private _viewportRuler: ViewportRuler,
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>) {}
/**
* Turns an element into a draggable item.
* @param element Element to which to attach the dragging functionality.
* @param config Object used to configure the dragging behavior.
*/
createDrag<T = any>(element: ElementRef<HTMLElement> | HTMLElement,
config: DragRefConfig = DEFAULT_CONFIG): DragRef<T> {
return new DragRef<T>(element, config, this._document, this._ngZone, this._viewportRuler,
this._dragDropRegistry);
}
/**
* Turns an element into a drop list.
* @param element Element to which to attach the drop list functionality.
*/
createDropList<T = any>(element: ElementRef<HTMLElement> | HTMLElement): DropListRef<T> {
return new DropListRef<T>(element, this._dragDropRegistry, this._document);
}
}
+64 -43
View File
@@ -8,7 +8,7 @@
import {EmbeddedViewRef, ElementRef, NgZone, ViewContainerRef, TemplateRef} from '@angular/core';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {Directionality} from '@angular/cdk/bidi';
import {Direction} from '@angular/cdk/bidi';
import {normalizePassiveListenerOptions} from '@angular/cdk/platform';
import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion';
import {Subscription, Subject, Observable, Observer} from 'rxjs';
@@ -57,6 +57,13 @@ const MOUSE_EVENT_IGNORE_TIME = 800;
*/
export interface DragRefInternal extends DragRef {}
/** Template that can be used to create a drag helper element (e.g. a preview or a placeholder). */
interface DragHelperTemplate<T = any> {
template: TemplateRef<T> | null;
viewContainer: ViewContainerRef;
context: T;
}
/**
* Reference to a draggable item. Used to manipulate or dispose of the item.
* @docs-private
@@ -173,10 +180,10 @@ export class DragRef<T = any> {
private _boundaryRect?: ClientRect;
/** Element that will be used as a template to create the draggable item's preview. */
private _previewTemplate?: {template: TemplateRef<any> | null, context?: any};
private _previewTemplate?: DragHelperTemplate | null;
/** Template for placeholder element rendered to show where a draggable would be dropped. */
private _placeholderTemplate?: {template: TemplateRef<any> | null, context?: any};
private _placeholderTemplate?: DragHelperTemplate | null;
/** Elements that can be used to drag the draggable item. */
private _handles: HTMLElement[] = [];
@@ -184,12 +191,18 @@ export class DragRef<T = any> {
/** Registered handles that are currently disabled. */
private _disabledHandles = new Set<HTMLElement>();
/** Droppable container that the draggable is a part of. */
private _dropContainer?: DropListRef;
/** Layout direction of the item. */
private _direction: Direction = 'ltr';
/** Axis along which dragging is locked. */
lockAxis: 'x' | 'y';
/** Whether starting to drag this element is disabled. */
get disabled(): boolean {
return this._disabled || !!(this.dropContainer && this.dropContainer.disabled);
return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
}
set disabled(value: boolean) {
const newValue = coerceBooleanProperty(value);
@@ -253,15 +266,11 @@ export class DragRef<T = any> {
constructor(
element: ElementRef<HTMLElement> | HTMLElement,
private _config: DragRefConfig,
private _document: Document,
private _ngZone: NgZone,
private _viewContainerRef: ViewContainerRef,
private _viewportRuler: ViewportRuler,
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
private _config: DragRefConfig,
/** Droppable container that the draggable is a part of. */
public dropContainer?: DropListRef,
private _dir?: Directionality) {
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>) {
this.withRootElement(element);
_dragDropRegistry.registerDragItem(this);
@@ -291,20 +300,18 @@ export class DragRef<T = any> {
/**
* Registers the template that should be used for the drag preview.
* @param template Template that from which to stamp out the preview.
* @param context Variables to add to the template's context.
*/
withPreviewTemplate(template: TemplateRef<any> | null, context?: any): this {
this._previewTemplate = {template, context};
withPreviewTemplate(template: DragHelperTemplate | null): this {
this._previewTemplate = template;
return this;
}
/**
* Registers the template that should be used for the drag placeholder.
* @param template Template that from which to stamp out the placeholder.
* @param context Variables to add to the template's context.
*/
withPlaceholderTemplate(template: TemplateRef<any> | null, context?: any): this {
this._placeholderTemplate = {template, context};
withPlaceholderTemplate(template: DragHelperTemplate | null): this {
this._placeholderTemplate = template;
return this;
}
@@ -364,6 +371,7 @@ export class DragRef<T = any> {
this._moveEvents.complete();
this._handles = [];
this._disabledHandles.clear();
this._dropContainer = undefined;
this._boundaryElement = this._rootElement = this._placeholderTemplate =
this._previewTemplate = this._nextSibling = null!;
}
@@ -398,6 +406,17 @@ export class DragRef<T = any> {
this._disabledHandles.delete(handle);
}
/** Sets the layout direction of the draggable item. */
withDirection(direction: Direction): this {
this._direction = direction;
return this;
}
/** Sets the container that the item is part of. */
_withDropContainer(container: DropListRef) {
this._dropContainer = container;
}
/** Unsubscribes from the global subscriptions. */
private _removeSubscriptions() {
this._pointerMoveSubscription.unsubscribe();
@@ -482,7 +501,7 @@ export class DragRef<T = any> {
event.preventDefault();
this._updatePointerDirectionDelta(constrainedPointerPosition);
if (this.dropContainer) {
if (this._dropContainer) {
this._updateActiveDropContainer(constrainedPointerPosition);
} else {
const activeTransform = this._activeTransform;
@@ -543,7 +562,7 @@ export class DragRef<T = any> {
this.released.next({source: this});
if (!this.dropContainer) {
if (!this._dropContainer) {
// Convert the active transform into a passive one. This means that next time
// the user starts dragging the item, its position will be calculated relatively
// to the new passive transform.
@@ -569,7 +588,7 @@ export class DragRef<T = any> {
this._lastTouchEventTime = Date.now();
}
if (this.dropContainer) {
if (this._dropContainer) {
const element = this._rootElement;
// Grab the `nextSibling` before the preview and placeholder
@@ -585,7 +604,7 @@ export class DragRef<T = any> {
element.style.display = 'none';
this._document.body.appendChild(element.parentNode!.replaceChild(placeholder, element));
this._document.body.appendChild(preview);
this.dropContainer.start();
this._dropContainer.start();
}
}
@@ -639,7 +658,7 @@ export class DragRef<T = any> {
this._toggleNativeDragInteractions();
this._hasStartedDragging = this._hasMoved = false;
this._initialContainer = this.dropContainer!;
this._initialContainer = this._dropContainer!;
this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
this._scrollPosition = this._viewportRuler.getViewportScrollPosition();
@@ -670,7 +689,7 @@ export class DragRef<T = any> {
if (this._nextSibling) {
this._nextSibling.parentNode!.insertBefore(this._rootElement, this._nextSibling);
} else {
this._initialContainer.element.nativeElement.appendChild(this._rootElement);
this._initialContainer.element.appendChild(this._rootElement);
}
this._destroyPreview();
@@ -679,7 +698,7 @@ export class DragRef<T = any> {
// Re-enter the NgZone since we bound `document` events on the outside.
this._ngZone.run(() => {
const container = this.dropContainer!;
const container = this._dropContainer!;
const currentIndex = container.getItemIndex(this);
const {x, y} = this._getPointerPositionOnPage(event);
const isPointerOverContainer = container._isOverContainer(x, y);
@@ -694,7 +713,7 @@ export class DragRef<T = any> {
isPointerOverContainer
});
container.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
this.dropContainer = this._initialContainer;
this._dropContainer = this._initialContainer;
});
}
@@ -704,31 +723,31 @@ export class DragRef<T = any> {
*/
private _updateActiveDropContainer({x, y}: Point) {
// Drop container that draggable has been moved into.
let newContainer = this.dropContainer!._getSiblingContainerFromPosition(this, x, y) ||
let newContainer = this._dropContainer!._getSiblingContainerFromPosition(this, x, y) ||
this._initialContainer._getSiblingContainerFromPosition(this, x, y);
// If we couldn't find a new container to move the item into, and the item has left it's
// initial container, check whether the it's over the initial container. This handles the
// case where two containers are connected one way and the user tries to undo dragging an
// item into a new container.
if (!newContainer && this.dropContainer !== this._initialContainer &&
if (!newContainer && this._dropContainer !== this._initialContainer &&
this._initialContainer._isOverContainer(x, y)) {
newContainer = this._initialContainer;
}
if (newContainer && newContainer !== this.dropContainer) {
if (newContainer && newContainer !== this._dropContainer) {
this._ngZone.run(() => {
// Notify the old container that the item has left.
this.exited.next({item: this, container: this.dropContainer!});
this.dropContainer!.exit(this);
this.exited.next({item: this, container: this._dropContainer!});
this._dropContainer!.exit(this);
// Notify the new container that the item has entered.
this.entered.next({item: this, container: newContainer!});
this.dropContainer = newContainer!;
this.dropContainer.enter(this, x, y);
this._dropContainer = newContainer!;
this._dropContainer.enter(this, x, y);
});
}
this.dropContainer!._sortItem(this, x, y, this._pointerDirectionDelta);
this._dropContainer!._sortItem(this, x, y, this._pointerDirectionDelta);
this._preview.style.transform =
getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
}
@@ -738,12 +757,13 @@ export class DragRef<T = any> {
* and will be used as a preview of the element that is being dragged.
*/
private _createPreviewElement(): HTMLElement {
const previewTemplate = this._previewTemplate;
const previewConfig = this._previewTemplate;
const previewTemplate = previewConfig ? previewConfig.template : null;
let preview: HTMLElement;
if (previewTemplate && previewTemplate.template) {
const viewRef = this._viewContainerRef.createEmbeddedView(previewTemplate.template,
previewTemplate.context);
if (previewTemplate) {
const viewRef = previewConfig!.viewContainer.createEmbeddedView(previewTemplate,
previewConfig!.context);
preview = viewRef.rootNodes[0];
this._previewRef = viewRef;
preview.style.transform =
@@ -771,7 +791,7 @@ export class DragRef<T = any> {
toggleNativeDragInteractions(preview, false);
preview.classList.add('cdk-drag-preview');
preview.setAttribute('dir', this._dir ? this._dir.value : 'ltr');
preview.setAttribute('dir', this._direction);
return preview;
}
@@ -825,13 +845,14 @@ export class DragRef<T = any> {
/** Creates an element that will be shown instead of the current element while dragging. */
private _createPlaceholderElement(): HTMLElement {
const placeholderTemplate = this._placeholderTemplate;
const placeholderConfig = this._placeholderTemplate;
const placeholderTemplate = placeholderConfig ? placeholderConfig.template : null;
let placeholder: HTMLElement;
if (placeholderTemplate && placeholderTemplate.template) {
this._placeholderRef = this._viewContainerRef.createEmbeddedView(
placeholderTemplate.template,
placeholderTemplate.context
if (placeholderTemplate) {
this._placeholderRef = placeholderConfig!.viewContainer.createEmbeddedView(
placeholderTemplate,
placeholderConfig!.context
);
placeholder = this._placeholderRef.rootNodes[0];
} else {
@@ -877,7 +898,7 @@ export class DragRef<T = any> {
/** Gets the pointer position on the page, accounting for any position constraints. */
private _getConstrainedPointerPosition(event: MouseEvent | TouchEvent): Point {
const point = this._getPointerPositionOnPage(event);
const dropContainerLock = this.dropContainer ? this.dropContainer.lockAxis : null;
const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
if (this.lockAxis === 'x' || dropContainerLock === 'x') {
point.y = this._pickupPositionOnPage.y;
+21 -10
View File
@@ -8,7 +8,7 @@
import {ElementRef} from '@angular/core';
import {DragDropRegistry} from './drag-drop-registry';
import {Directionality} from '@angular/cdk/bidi';
import {Direction} from '@angular/cdk/bidi';
import {Subject} from 'rxjs';
import {moveItemInArray} from './drag-utils';
import {DragRefInternal as DragRef} from './drag-ref';
@@ -50,6 +50,9 @@ export interface DropListRefInternal extends DropListRef {}
export class DropListRef<T = any> {
private _document: Document;
/** Element that the drop list is attached to. */
readonly element: HTMLElement;
/**
* Unique ID for the drop list.
* @deprecated No longer being used. To be removed.
@@ -138,13 +141,16 @@ export class DropListRef<T = any> {
/** Connected siblings that currently have a dragged item. */
private _activeSiblings = new Set<DropListRef>();
/** Layout direction of the drop list. */
private _direction: Direction = 'ltr';
constructor(
public element: ElementRef<HTMLElement>,
element: ElementRef<HTMLElement> | HTMLElement,
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
_document: any,
private _dir?: Directionality) {
_document: any) {
_dragDropRegistry.registerDropContainer(this);
this._document = _document;
this.element = element instanceof ElementRef ? element.nativeElement : element;
}
/** Removes the drop list functionality from the DOM element. */
@@ -203,7 +209,7 @@ export class DropListRef<T = any> {
element.parentElement!.insertBefore(placeholder, element);
this._activeDraggables.splice(newIndex, 0, item);
} else {
this.element.nativeElement.appendChild(placeholder);
this.element.appendChild(placeholder);
this._activeDraggables.push(item);
}
@@ -251,6 +257,13 @@ export class DropListRef<T = any> {
*/
withItems(items: DragRef[]): this {
this._draggables = items.slice();
items.forEach(item => item._withDropContainer(this));
return this;
}
/** Sets the layout direction of the drop list. */
withDirection(direction: Direction): this {
this._direction = direction;
return this;
}
@@ -285,7 +298,7 @@ export class DropListRef<T = any> {
// Items are sorted always by top/left in the cache, however they flow differently in RTL.
// The rest of the logic still stands no matter what orientation we're in, however
// we need to invert the array when determining the index.
const items = this._orientation === 'horizontal' && this._dir && this._dir.value === 'rtl' ?
const items = this._orientation === 'horizontal' && this._direction === 'rtl' ?
this._itemPositions.slice().reverse() : this._itemPositions;
return findIndex(items, currentItem => currentItem.drag === item);
@@ -382,7 +395,7 @@ export class DropListRef<T = any> {
/** Caches the position of the drop list. */
private _cacheOwnPosition() {
this._clientRect = this.element.nativeElement.getBoundingClientRect();
this._clientRect = this.element.getBoundingClientRect();
}
/** Refreshes the position cache of the items and sibling containers. */
@@ -574,15 +587,13 @@ export class DropListRef<T = any> {
return false;
}
const element = this.element.nativeElement;
// The `ClientRect`, that we're using to find the container over which the user is
// hovering, doesn't give us any information on whether the element has been scrolled
// out of the view or whether it's overlapping with other containers. This means that
// we could end up transferring the item into a container that's invisible or is positioned
// below another one. We use the result from `elementFromPoint` to get the top-most element
// at the pointer position and to find whether it's one of the intersecting drop containers.
return elementFromPoint === element || element.contains(elementFromPoint);
return elementFromPoint === this.element || this.element.contains(elementFromPoint);
}
/**
+4 -4
View File
@@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
// TODO(crisbeto): export once API is finalized
// export * from './drag-ref';
// export * from './drop-list-ref';
export {DragDrop} from './drag-drop';
export {DragRef, DragRefConfig} from './drag-ref';
export {DropListRef} from './drop-list-ref';
export * from './drop-list-container';
export * from './drag-events';
@@ -16,7 +16,7 @@ export * from './drag-utils';
export * from './drag-drop-module';
export * from './drag-drop-registry';
export * from './directives/drop-list';
export {CdkDropList} from './directives/drop-list';
export * from './directives/drop-list-group';
export * from './directives/drag';
export * from './directives/drag-handle';
+131 -6
View File
@@ -27,7 +27,8 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
started: EventEmitter<CdkDragStart>;
constructor(
element: ElementRef<HTMLElement>,
dropContainer: CdkDropList, _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, _viewportRuler: ViewportRuler, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _config: DragRefConfig, _dir: Directionality);
dropContainer: CdkDropList, _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, viewportRuler: ViewportRuler, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, config: DragRefConfig, _dir: Directionality,
dragDrop?: DragDrop);
getPlaceholderElement(): HTMLElement;
getRootElement(): HTMLElement;
ngAfterViewInit(): void;
@@ -111,7 +112,7 @@ export interface CdkDragStart<T = any> {
source: CdkDrag<T>;
}
export declare class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
export declare class CdkDropList<T = any> implements CdkDropListContainer, AfterContentInit, OnDestroy {
_draggables: QueryList<CdkDrag>;
_dropListRef: DropListRef<CdkDropList<T>>;
connectedTo: (CdkDropList | string)[] | CdkDropList | string;
@@ -126,7 +127,9 @@ export declare class CdkDropList<T = any> implements CdkDropListContainer, OnDes
lockAxis: 'x' | 'y';
orientation: 'horizontal' | 'vertical';
sorted: EventEmitter<CdkDragSortEvent<T>>;
constructor(element: ElementRef<HTMLElement>, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _changeDetectorRef: ChangeDetectorRef, dir?: Directionality, _group?: CdkDropListGroup<CdkDropList<any>> | undefined, _document?: any);
constructor(
element: ElementRef<HTMLElement>, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup<CdkDropList<any>> | undefined, _document?: any,
dragDrop?: DragDrop);
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropListContainer | null;
_isOverContainer(x: number, y: number): boolean;
_sortItem(item: CdkDrag, pointerX: number, pointerY: number, pointerDelta: {
@@ -137,6 +140,7 @@ export declare class CdkDropList<T = any> implements CdkDropListContainer, OnDes
enter(item: CdkDrag, pointerX: number, pointerY: number): void;
exit(item: CdkDrag): void;
getItemIndex(item: CdkDrag): number;
ngAfterContentInit(): void;
ngOnDestroy(): void;
start(): void;
}
@@ -168,11 +172,14 @@ export declare class CdkDropListGroup<T> implements OnDestroy {
ngOnDestroy(): void;
}
export interface CdkDropListInternal extends CdkDropList {
}
export declare function copyArrayItem<T = any>(currentArray: T[], targetArray: T[], currentIndex: number, targetIndex: number): void;
export declare class DragDrop {
constructor(_document: any, _ngZone: NgZone, _viewportRuler: ViewportRuler, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>);
createDrag<T = any>(element: ElementRef<HTMLElement> | HTMLElement, config?: DragRefConfig): DragRef<T>;
createDropList<T = any>(element: ElementRef<HTMLElement> | HTMLElement): DropListRef<T>;
}
export declare class DragDropModule {
}
@@ -193,6 +200,124 @@ export declare class DragDropRegistry<I, C extends {
stopDragging(drag: I): void;
}
export declare class DragRef<T = any> {
beforeStarted: Subject<void>;
data: T;
disabled: boolean;
dropped: Subject<{
previousIndex: number;
currentIndex: number;
item: DragRef<any>;
container: DropListRef;
previousContainer: DropListRef;
isPointerOverContainer: boolean;
}>;
ended: Subject<{
source: DragRef<any>;
}>;
entered: Subject<{
container: DropListRef;
item: DragRef<any>;
}>;
exited: Subject<{
container: DropListRef;
item: DragRef<any>;
}>;
lockAxis: 'x' | 'y';
moved: Observable<{
source: DragRef;
pointerPosition: {
x: number;
y: number;
};
event: MouseEvent | TouchEvent;
delta: {
x: -1 | 0 | 1;
y: -1 | 0 | 1;
};
}>;
released: Subject<{
source: DragRef<any>;
}>;
started: Subject<{
source: DragRef<any>;
}>;
constructor(element: ElementRef<HTMLElement> | HTMLElement, _config: DragRefConfig, _document: Document, _ngZone: NgZone, _viewportRuler: ViewportRuler, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>);
_withDropContainer(container: DropListRef): void;
disableHandle(handle: HTMLElement): void;
dispose(): void;
enableHandle(handle: HTMLElement): void;
getPlaceholderElement(): HTMLElement;
getRootElement(): HTMLElement;
isDragging(): boolean;
reset(): void;
withBoundaryElement(boundaryElement: ElementRef<HTMLElement> | HTMLElement | null): this;
withDirection(direction: Direction): this;
withHandles(handles: (HTMLElement | ElementRef<HTMLElement>)[]): this;
withPlaceholderTemplate(template: DragHelperTemplate | null): this;
withPreviewTemplate(template: DragHelperTemplate | null): this;
withRootElement(rootElement: ElementRef<HTMLElement> | HTMLElement): this;
}
export interface DragRefConfig {
dragStartThreshold: number;
pointerDirectionChangeThreshold: number;
}
export declare class DropListRef<T = any> {
beforeStarted: Subject<void>;
data: T;
disabled: boolean;
dropped: Subject<{
item: DragRef;
currentIndex: number;
previousIndex: number;
container: DropListRef<any>;
previousContainer: DropListRef<any>;
isPointerOverContainer: boolean;
}>;
readonly element: HTMLElement;
enterPredicate: (drag: DragRef, drop: DropListRef) => boolean;
entered: Subject<{
item: DragRef;
container: DropListRef<any>;
}>;
exited: Subject<{
item: DragRef;
container: DropListRef<any>;
}>;
id: string;
lockAxis: 'x' | 'y';
sorted: Subject<{
previousIndex: number;
currentIndex: number;
container: DropListRef<any>;
item: DragRef;
}>;
constructor(element: ElementRef<HTMLElement> | HTMLElement, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _document: any);
_canReceive(item: DragRef, x: number, y: number): boolean;
_getSiblingContainerFromPosition(item: DragRef, x: number, y: number): DropListRef | undefined;
_isOverContainer(x: number, y: number): boolean;
_sortItem(item: DragRef, pointerX: number, pointerY: number, pointerDelta: {
x: number;
y: number;
}): void;
_startReceiving(sibling: DropListRef): void;
_stopReceiving(sibling: DropListRef): void;
connectedTo(connectedTo: DropListRef[]): this;
dispose(): void;
drop(item: DragRef, currentIndex: number, previousContainer: DropListRef, isPointerOverContainer: boolean): void;
enter(item: DragRef, pointerX: number, pointerY: number): void;
exit(item: DragRef): void;
getItemIndex(item: DragRef): number;
isDragging(): boolean;
isReceiving(): boolean;
start(): void;
withDirection(direction: Direction): this;
withItems(items: DragRef[]): this;
withOrientation(orientation: 'vertical' | 'horizontal'): this;
}
export declare function moveItemInArray<T = any>(array: T[], fromIndex: number, toIndex: number): void;
export declare function transferArrayItem<T = any>(currentArray: T[], targetArray: T[], currentIndex: number, targetIndex: number): void;