@angular/cdk
Version:
Angular Material Component Development Kit
1,338 lines • 134 kB
JavaScript
/**
* @fileoverview added by tsickle
* Generated from: src/cdk/drag-drop/drop-list-ref.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @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 { coerceElement } from '@angular/cdk/coercion';
import { _supportsShadowDom } from '@angular/cdk/platform';
import { Subject, Subscription, interval, animationFrameScheduler } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { moveItemInArray } from './drag-utils';
/**
* Proximity, as a ratio to width/height, at which a
* dragged item will affect the drop container.
* @type {?}
*/
const DROP_PROXIMITY_THRESHOLD = 0.05;
/**
* Proximity, as a ratio to width/height at which to start auto-scrolling the drop list or the
* viewport. The value comes from trying it out manually until it feels right.
* @type {?}
*/
const SCROLL_PROXIMITY_THRESHOLD = 0.05;
/**
* Number of pixels to scroll for each frame when auto-scrolling an element.
* The value comes from trying it out manually until it feels right.
* @type {?}
*/
const AUTO_SCROLL_STEP = 2;
/**
* Entry in the position cache for draggable items.
* \@docs-private
* @record
*/
function CachedItemPosition() { }
if (false) {
/**
* Instance of the drag item.
* @type {?}
*/
CachedItemPosition.prototype.drag;
/**
* Dimensions of the item.
* @type {?}
*/
CachedItemPosition.prototype.clientRect;
/**
* Amount by which the item has been moved since dragging started.
* @type {?}
*/
CachedItemPosition.prototype.offset;
}
/**
* Object holding the scroll position of something.
* @record
*/
function ScrollPosition() { }
if (false) {
/** @type {?} */
ScrollPosition.prototype.top;
/** @type {?} */
ScrollPosition.prototype.left;
}
/** @enum {number} */
const AutoScrollVerticalDirection = {
NONE: 0, UP: 1, DOWN: 2,
};
/** @enum {number} */
const AutoScrollHorizontalDirection = {
NONE: 0, LEFT: 1, RIGHT: 2,
};
/**
* Internal compile-time-only representation of a `DropListRef`.
* Used to avoid circular import issues between the `DropListRef` and the `DragRef`.
* \@docs-private
* @record
*/
export function DropListRefInternal() { }
/**
* Reference to a drop list. Used to manipulate or dispose of the container.
* @template T
*/
export class DropListRef {
/**
* @param {?} element
* @param {?} _dragDropRegistry
* @param {?} _document
* @param {?} _ngZone
* @param {?} _viewportRuler
*/
constructor(element, _dragDropRegistry, _document, _ngZone, _viewportRuler) {
this._dragDropRegistry = _dragDropRegistry;
this._ngZone = _ngZone;
this._viewportRuler = _viewportRuler;
/**
* Whether starting a dragging sequence from this container is disabled.
*/
this.disabled = false;
/**
* Whether sorting items within the list is disabled.
*/
this.sortingDisabled = false;
/**
* Whether auto-scrolling the view when the user
* moves their pointer close to the edges is disabled.
*/
this.autoScrollDisabled = false;
/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
*/
this.enterPredicate = (/**
* @return {?}
*/
() => true);
/**
* Emits right before dragging has started.
*/
this.beforeStarted = new Subject();
/**
* Emits when the user has moved a new drag item into this container.
*/
this.entered = new Subject();
/**
* Emits when the user removes an item from the container
* by dragging it into another container.
*/
this.exited = new Subject();
/**
* Emits when the user drops an item inside the container.
*/
this.dropped = new Subject();
/**
* Emits as the user is swapping items while actively dragging.
*/
this.sorted = new Subject();
/**
* Whether an item in the list is being dragged.
*/
this._isDragging = false;
/**
* Cache of the dimensions of all the items inside the container.
*/
this._itemPositions = [];
/**
* Keeps track of the container's scroll position.
*/
this._scrollPosition = { top: 0, left: 0 };
/**
* Keeps track of the scroll position of the viewport.
*/
this._viewportScrollPosition = { top: 0, left: 0 };
/**
* Keeps track of the item that was last swapped with the dragged item, as
* well as what direction the pointer was moving in when the swap occured.
*/
this._previousSwap = { drag: (/** @type {?} */ (null)), delta: 0 };
/**
* Drop lists that are connected to the current one.
*/
this._siblings = [];
/**
* Direction in which the list is oriented.
*/
this._orientation = 'vertical';
/**
* Connected siblings that currently have a dragged item.
*/
this._activeSiblings = new Set();
/**
* Layout direction of the drop list.
*/
this._direction = 'ltr';
/**
* Subscription to the window being scrolled.
*/
this._viewportScrollSubscription = Subscription.EMPTY;
/**
* Vertical direction in which the list is currently scrolling.
*/
this._verticalScrollDirection = 0 /* NONE */;
/**
* Horizontal direction in which the list is currently scrolling.
*/
this._horizontalScrollDirection = 0 /* NONE */;
/**
* Used to signal to the current auto-scroll sequence when to stop.
*/
this._stopScrollTimers = new Subject();
/**
* Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly.
*/
this._cachedShadowRoot = null;
/**
* Handles the container being scrolled. Has to be an arrow function to preserve the context.
*/
this._handleScroll = (/**
* @return {?}
*/
() => {
if (!this.isDragging()) {
return;
}
/** @type {?} */
const element = coerceElement(this.element);
this._updateAfterScroll(this._scrollPosition, element.scrollTop, element.scrollLeft);
});
/**
* Starts the interval that'll auto-scroll the element.
*/
this._startScrollInterval = (/**
* @return {?}
*/
() => {
this._stopScrolling();
interval(0, animationFrameScheduler)
.pipe(takeUntil(this._stopScrollTimers))
.subscribe((/**
* @return {?}
*/
() => {
/** @type {?} */
const node = this._scrollNode;
if (this._verticalScrollDirection === 1 /* UP */) {
incrementVerticalScroll(node, -AUTO_SCROLL_STEP);
}
else if (this._verticalScrollDirection === 2 /* DOWN */) {
incrementVerticalScroll(node, AUTO_SCROLL_STEP);
}
if (this._horizontalScrollDirection === 1 /* LEFT */) {
incrementHorizontalScroll(node, -AUTO_SCROLL_STEP);
}
else if (this._horizontalScrollDirection === 2 /* RIGHT */) {
incrementHorizontalScroll(node, AUTO_SCROLL_STEP);
}
}));
});
this.element = coerceElement(element);
this._document = _document;
_dragDropRegistry.registerDropContainer(this);
}
/**
* Removes the drop list functionality from the DOM element.
* @return {?}
*/
dispose() {
this._stopScrolling();
this._stopScrollTimers.complete();
this._removeListeners();
this.beforeStarted.complete();
this.entered.complete();
this.exited.complete();
this.dropped.complete();
this.sorted.complete();
this._activeSiblings.clear();
this._scrollNode = (/** @type {?} */ (null));
this._dragDropRegistry.removeDropContainer(this);
}
/**
* Whether an item from this list is currently being dragged.
* @return {?}
*/
isDragging() {
return this._isDragging;
}
/**
* Starts dragging an item.
* @return {?}
*/
start() {
/** @type {?} */
const element = coerceElement(this.element);
this.beforeStarted.next();
this._isDragging = true;
this._cacheItems();
this._siblings.forEach((/**
* @param {?} sibling
* @return {?}
*/
sibling => sibling._startReceiving(this)));
this._removeListeners();
this._ngZone.runOutsideAngular((/**
* @return {?}
*/
() => element.addEventListener('scroll', this._handleScroll)));
this._listenToScrollEvents();
}
/**
* Emits an event to indicate that the user moved an item into the container.
* @param {?} item Item that was moved into the container.
* @param {?} pointerX Position of the item along the X axis.
* @param {?} pointerY Position of the item along the Y axis.
* @return {?}
*/
enter(item, pointerX, pointerY) {
this.start();
// If sorting is disabled, we want the item to return to its starting
// position if the user is returning it to its initial container.
/** @type {?} */
let newIndex = this.sortingDisabled ? this._draggables.indexOf(item) : -1;
if (newIndex === -1) {
// We use the coordinates of where the item entered the drop
// zone to figure out at which index it should be inserted.
newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
}
/** @type {?} */
const activeDraggables = this._activeDraggables;
/** @type {?} */
const currentIndex = activeDraggables.indexOf(item);
/** @type {?} */
const placeholder = item.getPlaceholderElement();
/** @type {?} */
let newPositionReference = activeDraggables[newIndex];
// If the item at the new position is the same as the item that is being dragged,
// it means that we're trying to restore the item to its initial position. In this
// case we should use the next item from the list as the reference.
if (newPositionReference === item) {
newPositionReference = activeDraggables[newIndex + 1];
}
// Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
// into another container and back again), we have to ensure that it isn't duplicated.
if (currentIndex > -1) {
activeDraggables.splice(currentIndex, 1);
}
// Don't use items that are being dragged as a reference, because
// their element has been moved down to the bottom of the body.
if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
/** @type {?} */
const element = newPositionReference.getRootElement();
(/** @type {?} */ (element.parentElement)).insertBefore(placeholder, element);
activeDraggables.splice(newIndex, 0, item);
}
else {
coerceElement(this.element).appendChild(placeholder);
activeDraggables.push(item);
}
// The transform needs to be cleared so it doesn't throw off the measurements.
placeholder.style.transform = '';
// Note that the positions were already cached when we called `start` above,
// but we need to refresh them since the amount of items has changed.
this._cacheItemPositions();
this.entered.next({ item, container: this, currentIndex: this.getItemIndex(item) });
}
/**
* Removes an item from the container after it was dragged into another container by the user.
* @param {?} item Item that was dragged out.
* @return {?}
*/
exit(item) {
this._reset();
this.exited.next({ item, container: this });
}
/**
* Drops an item into this container.
* @param {?} item Item being dropped into the container.
* @param {?} currentIndex Index at which the item should be inserted.
* @param {?} previousContainer Container from which the item got dragged in.
* @param {?} isPointerOverContainer Whether the user's pointer was over the
* container when the item was dropped.
* @param {?} distance Distance the user has dragged since the start of the dragging sequence.
* @return {?}
*/
drop(item, currentIndex, previousContainer, isPointerOverContainer, distance) {
this._reset();
this.dropped.next({
item,
currentIndex,
previousIndex: previousContainer.getItemIndex(item),
container: this,
previousContainer,
isPointerOverContainer,
distance
});
}
/**
* Sets the draggable items that are a part of this list.
* @template THIS
* @this {THIS}
* @param {?} items Items that are a part of this list.
* @return {THIS}
*/
withItems(items) {
(/** @type {?} */ (this))._draggables = items;
items.forEach((/**
* @param {?} item
* @return {?}
*/
item => item._withDropContainer((/** @type {?} */ (this)))));
if ((/** @type {?} */ (this)).isDragging()) {
(/** @type {?} */ (this))._cacheItems();
}
return (/** @type {?} */ (this));
}
/**
* Sets the layout direction of the drop list.
* @template THIS
* @this {THIS}
* @param {?} direction
* @return {THIS}
*/
withDirection(direction) {
(/** @type {?} */ (this))._direction = direction;
return (/** @type {?} */ (this));
}
/**
* Sets the containers that are connected to this one. When two or more containers are
* connected, the user will be allowed to transfer items between them.
* @template THIS
* @this {THIS}
* @param {?} connectedTo Other containers that the current containers should be connected to.
* @return {THIS}
*/
connectedTo(connectedTo) {
(/** @type {?} */ (this))._siblings = connectedTo.slice();
return (/** @type {?} */ (this));
}
/**
* Sets the orientation of the container.
* @template THIS
* @this {THIS}
* @param {?} orientation New orientation for the container.
* @return {THIS}
*/
withOrientation(orientation) {
(/** @type {?} */ (this))._orientation = orientation;
return (/** @type {?} */ (this));
}
/**
* Figures out the index of an item in the container.
* @param {?} item Item whose index should be determined.
* @return {?}
*/
getItemIndex(item) {
if (!this._isDragging) {
return this._draggables.indexOf(item);
}
// 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.
/** @type {?} */
const items = this._orientation === 'horizontal' && this._direction === 'rtl' ?
this._itemPositions.slice().reverse() : this._itemPositions;
return findIndex(items, (/**
* @param {?} currentItem
* @return {?}
*/
currentItem => currentItem.drag === item));
}
/**
* Whether the list is able to receive the item that
* is currently being dragged inside a connected drop list.
* @return {?}
*/
isReceiving() {
return this._activeSiblings.size > 0;
}
/**
* Sorts an item inside the container based on its position.
* @param {?} item Item to be sorted.
* @param {?} pointerX Position of the item along the X axis.
* @param {?} pointerY Position of the item along the Y axis.
* @param {?} pointerDelta Direction in which the pointer is moving along each axis.
* @return {?}
*/
_sortItem(item, pointerX, pointerY, pointerDelta) {
// Don't sort the item if sorting is disabled or it's out of range.
if (this.sortingDisabled || !this._isPointerNearDropContainer(pointerX, pointerY)) {
return;
}
/** @type {?} */
const siblings = this._itemPositions;
/** @type {?} */
const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
if (newIndex === -1 && siblings.length > 0) {
return;
}
/** @type {?} */
const isHorizontal = this._orientation === 'horizontal';
/** @type {?} */
const currentIndex = findIndex(siblings, (/**
* @param {?} currentItem
* @return {?}
*/
currentItem => currentItem.drag === item));
/** @type {?} */
const siblingAtNewPosition = siblings[newIndex];
/** @type {?} */
const currentPosition = siblings[currentIndex].clientRect;
/** @type {?} */
const newPosition = siblingAtNewPosition.clientRect;
/** @type {?} */
const delta = currentIndex > newIndex ? 1 : -1;
this._previousSwap.drag = siblingAtNewPosition.drag;
this._previousSwap.delta = isHorizontal ? pointerDelta.x : pointerDelta.y;
// How many pixels the item's placeholder should be offset.
/** @type {?} */
const itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta);
// How many pixels all the other items should be offset.
/** @type {?} */
const siblingOffset = this._getSiblingOffsetPx(currentIndex, siblings, delta);
// Save the previous order of the items before moving the item to its new index.
// We use this to check whether an item has been moved as a result of the sorting.
/** @type {?} */
const oldOrder = siblings.slice();
// Shuffle the array in place.
moveItemInArray(siblings, currentIndex, newIndex);
this.sorted.next({
previousIndex: currentIndex,
currentIndex: newIndex,
container: this,
item
});
siblings.forEach((/**
* @param {?} sibling
* @param {?} index
* @return {?}
*/
(sibling, index) => {
// Don't do anything if the position hasn't changed.
if (oldOrder[index] === sibling) {
return;
}
/** @type {?} */
const isDraggedItem = sibling.drag === item;
/** @type {?} */
const offset = isDraggedItem ? itemOffset : siblingOffset;
/** @type {?} */
const elementToOffset = isDraggedItem ? item.getPlaceholderElement() :
sibling.drag.getRootElement();
// Update the offset to reflect the new position.
sibling.offset += offset;
// Since we're moving the items with a `transform`, we need to adjust their cached
// client rects to reflect their new position, as well as swap their positions in the cache.
// Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the
// elements may be mid-animation which will give us a wrong result.
if (isHorizontal) {
// Round the transforms since some browsers will
// blur the elements, for sub-pixel transforms.
elementToOffset.style.transform = `translate3d(${Math.round(sibling.offset)}px, 0, 0)`;
adjustClientRect(sibling.clientRect, 0, offset);
}
else {
elementToOffset.style.transform = `translate3d(0, ${Math.round(sibling.offset)}px, 0)`;
adjustClientRect(sibling.clientRect, offset, 0);
}
}));
}
/**
* Checks whether the user's pointer is close to the edges of either the
* viewport or the drop list and starts the auto-scroll sequence.
* @param {?} pointerX User's pointer position along the x axis.
* @param {?} pointerY User's pointer position along the y axis.
* @return {?}
*/
_startScrollingIfNecessary(pointerX, pointerY) {
if (this.autoScrollDisabled) {
return;
}
/** @type {?} */
let scrollNode;
/** @type {?} */
let verticalScrollDirection = 0 /* NONE */;
/** @type {?} */
let horizontalScrollDirection = 0 /* NONE */;
// Check whether we should start scrolling the container.
if (this._isPointerNearDropContainer(pointerX, pointerY)) {
/** @type {?} */
const element = coerceElement(this.element);
[verticalScrollDirection, horizontalScrollDirection] =
getElementScrollDirections(element, this._clientRect, pointerX, pointerY);
if (verticalScrollDirection || horizontalScrollDirection) {
scrollNode = element;
}
}
// Otherwise check if we can start scrolling the viewport.
if (!verticalScrollDirection && !horizontalScrollDirection) {
const { width, height } = this._viewportRuler.getViewportSize();
/** @type {?} */
const clientRect = { width, height, top: 0, right: width, bottom: height, left: 0 };
verticalScrollDirection = getVerticalScrollDirection(clientRect, pointerY);
horizontalScrollDirection = getHorizontalScrollDirection(clientRect, pointerX);
scrollNode = window;
}
if (scrollNode && (verticalScrollDirection !== this._verticalScrollDirection ||
horizontalScrollDirection !== this._horizontalScrollDirection ||
scrollNode !== this._scrollNode)) {
this._verticalScrollDirection = verticalScrollDirection;
this._horizontalScrollDirection = horizontalScrollDirection;
this._scrollNode = scrollNode;
if ((verticalScrollDirection || horizontalScrollDirection) && scrollNode) {
this._ngZone.runOutsideAngular(this._startScrollInterval);
}
else {
this._stopScrolling();
}
}
}
/**
* Stops any currently-running auto-scroll sequences.
* @return {?}
*/
_stopScrolling() {
this._stopScrollTimers.next();
}
/**
* Caches the position of the drop list.
* @private
* @return {?}
*/
_cacheOwnPosition() {
/** @type {?} */
const element = coerceElement(this.element);
this._clientRect = getMutableClientRect(element);
this._scrollPosition = { top: element.scrollTop, left: element.scrollLeft };
}
/**
* Refreshes the position cache of the items and sibling containers.
* @private
* @return {?}
*/
_cacheItemPositions() {
/** @type {?} */
const isHorizontal = this._orientation === 'horizontal';
this._itemPositions = this._activeDraggables.map((/**
* @param {?} drag
* @return {?}
*/
drag => {
/** @type {?} */
const elementToMeasure = this._dragDropRegistry.isDragging(drag) ?
// If the element is being dragged, we have to measure the
// placeholder, because the element is hidden.
drag.getPlaceholderElement() :
drag.getRootElement();
return { drag, offset: 0, clientRect: getMutableClientRect(elementToMeasure) };
})).sort((/**
* @param {?} a
* @param {?} b
* @return {?}
*/
(a, b) => {
return isHorizontal ? a.clientRect.left - b.clientRect.left :
a.clientRect.top - b.clientRect.top;
}));
}
/**
* Resets the container to its initial state.
* @private
* @return {?}
*/
_reset() {
this._isDragging = false;
// TODO(crisbeto): may have to wait for the animations to finish.
this._activeDraggables.forEach((/**
* @param {?} item
* @return {?}
*/
item => item.getRootElement().style.transform = ''));
this._siblings.forEach((/**
* @param {?} sibling
* @return {?}
*/
sibling => sibling._stopReceiving(this)));
this._activeDraggables = [];
this._itemPositions = [];
this._previousSwap.drag = null;
this._previousSwap.delta = 0;
this._stopScrolling();
this._removeListeners();
}
/**
* Gets the offset in pixels by which the items that aren't being dragged should be moved.
* @private
* @param {?} currentIndex Index of the item currently being dragged.
* @param {?} siblings All of the items in the list.
* @param {?} delta Direction in which the user is moving.
* @return {?}
*/
_getSiblingOffsetPx(currentIndex, siblings, delta) {
/** @type {?} */
const isHorizontal = this._orientation === 'horizontal';
/** @type {?} */
const currentPosition = siblings[currentIndex].clientRect;
/** @type {?} */
const immediateSibling = siblings[currentIndex + delta * -1];
/** @type {?} */
let siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta;
if (immediateSibling) {
/** @type {?} */
const start = isHorizontal ? 'left' : 'top';
/** @type {?} */
const end = isHorizontal ? 'right' : 'bottom';
// Get the spacing between the start of the current item and the end of the one immediately
// after it in the direction in which the user is dragging, or vice versa. We add it to the
// offset in order to push the element to where it will be when it's inline and is influenced
// by the `margin` of its siblings.
if (delta === -1) {
siblingOffset -= immediateSibling.clientRect[start] - currentPosition[end];
}
else {
siblingOffset += currentPosition[start] - immediateSibling.clientRect[end];
}
}
return siblingOffset;
}
/**
* Checks whether the pointer coordinates are close to the drop container.
* @private
* @param {?} pointerX Coordinates along the X axis.
* @param {?} pointerY Coordinates along the Y axis.
* @return {?}
*/
_isPointerNearDropContainer(pointerX, pointerY) {
const { top, right, bottom, left, width, height } = this._clientRect;
/** @type {?} */
const xThreshold = width * DROP_PROXIMITY_THRESHOLD;
/** @type {?} */
const yThreshold = height * DROP_PROXIMITY_THRESHOLD;
return pointerY > top - yThreshold && pointerY < bottom + yThreshold &&
pointerX > left - xThreshold && pointerX < right + xThreshold;
}
/**
* Gets the offset in pixels by which the item that is being dragged should be moved.
* @private
* @param {?} currentPosition Current position of the item.
* @param {?} newPosition Position of the item where the current item should be moved.
* @param {?} delta Direction in which the user is moving.
* @return {?}
*/
_getItemOffsetPx(currentPosition, newPosition, delta) {
/** @type {?} */
const isHorizontal = this._orientation === 'horizontal';
/** @type {?} */
let itemOffset = isHorizontal ? newPosition.left - currentPosition.left :
newPosition.top - currentPosition.top;
// Account for differences in the item width/height.
if (delta === -1) {
itemOffset += isHorizontal ? newPosition.width - currentPosition.width :
newPosition.height - currentPosition.height;
}
return itemOffset;
}
/**
* Gets the index of an item in the drop container, based on the position of the user's pointer.
* @private
* @param {?} item Item that is being sorted.
* @param {?} pointerX Position of the user's pointer along the X axis.
* @param {?} pointerY Position of the user's pointer along the Y axis.
* @param {?=} delta Direction in which the user is moving their pointer.
* @return {?}
*/
_getItemIndexFromPointerPosition(item, pointerX, pointerY, delta) {
/** @type {?} */
const isHorizontal = this._orientation === 'horizontal';
return findIndex(this._itemPositions, (/**
* @param {?} __0
* @param {?} _
* @param {?} array
* @return {?}
*/
({ drag, clientRect }, _, array) => {
if (drag === item) {
// If there's only one item left in the container, it must be
// the dragged item itself so we use it as a reference.
return array.length < 2;
}
if (delta) {
/** @type {?} */
const direction = isHorizontal ? delta.x : delta.y;
// If the user is still hovering over the same item as last time, and they didn't change
// the direction in which they're dragging, we don't consider it a direction swap.
if (drag === this._previousSwap.drag && direction === this._previousSwap.delta) {
return false;
}
}
return isHorizontal ?
// Round these down since most browsers report client rects with
// sub-pixel precision, whereas the pointer coordinates are rounded to pixels.
pointerX >= Math.floor(clientRect.left) && pointerX <= Math.floor(clientRect.right) :
pointerY >= Math.floor(clientRect.top) && pointerY <= Math.floor(clientRect.bottom);
}));
}
/**
* Caches the current items in the list and their positions.
* @private
* @return {?}
*/
_cacheItems() {
this._activeDraggables = this._draggables.slice();
this._cacheItemPositions();
this._cacheOwnPosition();
}
/**
* Updates the internal state of the container after a scroll event has happened.
* @private
* @param {?} scrollPosition Object that is keeping track of the scroll position.
* @param {?} newTop New top scroll position.
* @param {?} newLeft New left scroll position.
* @param {?=} extraClientRect Extra `ClientRect` object that should be updated, in addition to the
* ones of the drag items. Useful when the viewport has been scrolled and we also need to update
* the `ClientRect` of the list.
* @return {?}
*/
_updateAfterScroll(scrollPosition, newTop, newLeft, extraClientRect) {
/** @type {?} */
const topDifference = scrollPosition.top - newTop;
/** @type {?} */
const leftDifference = scrollPosition.left - newLeft;
if (extraClientRect) {
adjustClientRect(extraClientRect, topDifference, leftDifference);
}
// Since we know the amount that the user has scrolled we can shift all of the client rectangles
// ourselves. This is cheaper than re-measuring everything and we can avoid inconsistent
// behavior where we might be measuring the element before its position has changed.
this._itemPositions.forEach((/**
* @param {?} __0
* @return {?}
*/
({ clientRect }) => {
adjustClientRect(clientRect, topDifference, leftDifference);
}));
// We need two loops for this, because we want all of the cached
// positions to be up-to-date before we re-sort the item.
this._itemPositions.forEach((/**
* @param {?} __0
* @return {?}
*/
({ drag }) => {
if (this._dragDropRegistry.isDragging(drag)) {
// We need to re-sort the item manually, because the pointer move
// events won't be dispatched while the user is scrolling.
drag._sortFromLastPointerPosition();
}
}));
scrollPosition.top = newTop;
scrollPosition.left = newLeft;
}
/**
* Removes the event listeners associated with this drop list.
* @private
* @return {?}
*/
_removeListeners() {
coerceElement(this.element).removeEventListener('scroll', this._handleScroll);
this._viewportScrollSubscription.unsubscribe();
}
/**
* Checks whether the user's pointer is positioned over the container.
* @param {?} x Pointer position along the X axis.
* @param {?} y Pointer position along the Y axis.
* @return {?}
*/
_isOverContainer(x, y) {
return isInsideClientRect(this._clientRect, x, y);
}
/**
* Figures out whether an item should be moved into a sibling
* drop container, based on its current position.
* @param {?} item Drag item that is being moved.
* @param {?} x Position of the item along the X axis.
* @param {?} y Position of the item along the Y axis.
* @return {?}
*/
_getSiblingContainerFromPosition(item, x, y) {
return this._siblings.find((/**
* @param {?} sibling
* @return {?}
*/
sibling => sibling._canReceive(item, x, y)));
}
/**
* Checks whether the drop list can receive the passed-in item.
* @param {?} item Item that is being dragged into the list.
* @param {?} x Position of the item along the X axis.
* @param {?} y Position of the item along the Y axis.
* @return {?}
*/
_canReceive(item, x, y) {
if (!isInsideClientRect(this._clientRect, x, y) || !this.enterPredicate(item, this)) {
return false;
}
/** @type {?} */
const elementFromPoint = (/** @type {?} */ (this._getShadowRoot().elementFromPoint(x, y)));
// If there's no element at the pointer position, then
// the client rect is probably scrolled out of the view.
if (!elementFromPoint) {
return false;
}
/** @type {?} */
const nativeElement = coerceElement(this.element);
// 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 === nativeElement || nativeElement.contains(elementFromPoint);
}
/**
* Called by one of the connected drop lists when a dragging sequence has started.
* @param {?} sibling Sibling in which dragging has started.
* @return {?}
*/
_startReceiving(sibling) {
/** @type {?} */
const activeSiblings = this._activeSiblings;
if (!activeSiblings.has(sibling)) {
activeSiblings.add(sibling);
this._cacheOwnPosition();
this._listenToScrollEvents();
}
}
/**
* Called by a connected drop list when dragging has stopped.
* @param {?} sibling Sibling whose dragging has stopped.
* @return {?}
*/
_stopReceiving(sibling) {
this._activeSiblings.delete(sibling);
this._viewportScrollSubscription.unsubscribe();
}
/**
* Starts listening to scroll events on the viewport.
* Used for updating the internal state of the list.
* @private
* @return {?}
*/
_listenToScrollEvents() {
this._viewportScrollPosition = (/** @type {?} */ (this._viewportRuler)).getViewportScrollPosition();
this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe((/**
* @return {?}
*/
() => {
if (this.isDragging()) {
/** @type {?} */
const newPosition = (/** @type {?} */ (this._viewportRuler)).getViewportScrollPosition();
this._updateAfterScroll(this._viewportScrollPosition, newPosition.top, newPosition.left, this._clientRect);
}
else if (this.isReceiving()) {
this._cacheOwnPosition();
}
}));
}
/**
* Lazily resolves and returns the shadow root of the element. We do this in a function, rather
* than saving it in property directly on init, because we want to resolve it as late as possible
* in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
* constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
* @private
* @return {?}
*/
_getShadowRoot() {
if (!this._cachedShadowRoot) {
this._cachedShadowRoot = getShadowRoot(coerceElement(this.element)) || this._document;
}
return this._cachedShadowRoot;
}
}
if (false) {
/**
* Element that the drop list is attached to.
* @type {?}
*/
DropListRef.prototype.element;
/**
* Whether starting a dragging sequence from this container is disabled.
* @type {?}
*/
DropListRef.prototype.disabled;
/**
* Whether sorting items within the list is disabled.
* @type {?}
*/
DropListRef.prototype.sortingDisabled;
/**
* Locks the position of the draggable elements inside the container along the specified axis.
* @type {?}
*/
DropListRef.prototype.lockAxis;
/**
* Whether auto-scrolling the view when the user
* moves their pointer close to the edges is disabled.
* @type {?}
*/
DropListRef.prototype.autoScrollDisabled;
/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
* @type {?}
*/
DropListRef.prototype.enterPredicate;
/**
* Emits right before dragging has started.
* @type {?}
*/
DropListRef.prototype.beforeStarted;
/**
* Emits when the user has moved a new drag item into this container.
* @type {?}
*/
DropListRef.prototype.entered;
/**
* Emits when the user removes an item from the container
* by dragging it into another container.
* @type {?}
*/
DropListRef.prototype.exited;
/**
* Emits when the user drops an item inside the container.
* @type {?}
*/
DropListRef.prototype.dropped;
/**
* Emits as the user is swapping items while actively dragging.
* @type {?}
*/
DropListRef.prototype.sorted;
/**
* Arbitrary data that can be attached to the drop list.
* @type {?}
*/
DropListRef.prototype.data;
/**
* Whether an item in the list is being dragged.
* @type {?}
* @private
*/
DropListRef.prototype._isDragging;
/**
* Cache of the dimensions of all the items inside the container.
* @type {?}
* @private
*/
DropListRef.prototype._itemPositions;
/**
* Keeps track of the container's scroll position.
* @type {?}
* @private
*/
DropListRef.prototype._scrollPosition;
/**
* Keeps track of the scroll position of the viewport.
* @type {?}
* @private
*/
DropListRef.prototype._viewportScrollPosition;
/**
* Cached `ClientRect` of the drop list.
* @type {?}
* @private
*/
DropListRef.prototype._clientRect;
/**
* Draggable items that are currently active inside the container. Includes the items
* from `_draggables`, as well as any items that have been dragged in, but haven't
* been dropped yet.
* @type {?}
* @private
*/
DropListRef.prototype._activeDraggables;
/**
* Keeps track of the item that was last swapped with the dragged item, as
* well as what direction the pointer was moving in when the swap occured.
* @type {?}
* @private
*/
DropListRef.prototype._previousSwap;
/**
* Draggable items in the container.
* @type {?}
* @private
*/
DropListRef.prototype._draggables;
/**
* Drop lists that are connected to the current one.
* @type {?}
* @private
*/
DropListRef.prototype._siblings;
/**
* Direction in which the list is oriented.
* @type {?}
* @private
*/
DropListRef.prototype._orientation;
/**
* Connected siblings that currently have a dragged item.
* @type {?}
* @private
*/
DropListRef.prototype._activeSiblings;
/**
* Layout direction of the drop list.
* @type {?}
* @private
*/
DropListRef.prototype._direction;
/**
* Subscription to the window being scrolled.
* @type {?}
* @private
*/
DropListRef.prototype._viewportScrollSubscription;
/**
* Vertical direction in which the list is currently scrolling.
* @type {?}
* @private
*/
DropListRef.prototype._verticalScrollDirection;
/**
* Horizontal direction in which the list is currently scrolling.
* @type {?}
* @private
*/
DropListRef.prototype._horizontalScrollDirection;
/**
* Node that is being auto-scrolled.
* @type {?}
* @private
*/
DropListRef.prototype._scrollNode;
/**
* Used to signal to the current auto-scroll sequence when to stop.
* @type {?}
* @private
*/
DropListRef.prototype._stopScrollTimers;
/**
* Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly.
* @type {?}
* @private
*/
DropListRef.prototype._cachedShadowRoot;
/**
* Reference to the document.
* @type {?}
* @private
*/
DropListRef.prototype._document;
/**
* Handles the container being scrolled. Has to be an arrow function to preserve the context.
* @type {?}
* @private
*/
DropListRef.prototype._handleScroll;
/**
* Starts the interval that'll auto-scroll the element.
* @type {?}
* @private
*/
DropListRef.prototype._startScrollInterval;
/**
* @type {?}
* @private
*/
DropListRef.prototype._dragDropRegistry;
/**
* @type {?}
* @private
*/
DropListRef.prototype._ngZone;
/**
* @type {?}
* @private
*/
DropListRef.prototype._viewportRuler;
}
/**
* Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
* @param {?} clientRect `ClientRect` that should be updated.
* @param {?} top Amount to add to the `top` position.
* @param {?} left Amount to add to the `left` position.
* @return {?}
*/
function adjustClientRect(clientRect, top, left) {
clientRect.top += top;
clientRect.bottom = clientRect.top + clientRect.height;
clientRect.left += left;
clientRect.right = clientRect.left + clientRect.width;
}
/**
* Finds the index of an item that matches a predicate function. Used as an equivalent
* of `Array.prototype.findIndex` which isn't part of the standard Google typings.
* @template T
* @param {?} array Array in which to look for matches.
* @param {?} predicate Function used to determine whether an item is a match.
* @return {?}
*/
function findIndex(array, predicate) {
for (let i = 0; i < array.length; i++) {
if (predicate(array[i], i, array)) {
return i;
}
}
return -1;
}
/**
* Checks whether some coordinates are within a `ClientRect`.
* @param {?} clientRect ClientRect that is being checked.
* @param {?} x Coordinates along the X axis.
* @param {?} y Coordinates along the Y axis.
* @return {?}
*/
function isInsideClientRect(clientRect, x, y) {
const { top, bottom, left, right } = clientRect;
return y >= top && y <= bottom && x >= left && x <= right;
}
/**
* Gets a mutable version of an element's bounding `ClientRect`.
* @param {?} element
* @return {?}
*/
function getMutableClientRect(element) {
/** @type {?} */
const clientRect = element.getBoundingClientRect();
// We need to clone the `clientRect` here, because all the values on it are readonly
// and we need to be able to update them. Also we can't use a spread here, because
// the values on a `ClientRect` aren't own properties. See:
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
return {
top: clientRect.top,
right: clientRect.right,
bottom: clientRect.bottom,
left: clientRect.left,
width: clientRect.width,
height: clientRect.height
};
}
/**
* Increments the vertical scroll position of a node.
* @param {?} node Node whose scroll position should change.
* @param {?} amount Amount of pixels that the `node` should be scrolled.
* @return {?}
*/
function incrementVerticalScroll(node, amount) {
if (node === window) {
((/** @type {?} */ (node))).scrollBy(0, amount);
}
else {
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
((/** @type {?} */ (node))).scrollTop += amount;
}
}
/**
* Increments the horizontal scroll position of a node.
* @param {?} node Node whose scroll position should change.
* @param {?} amount Amount of pixels that the `node` should be scrolled.
* @return {?}
*/
function incrementHorizontalScroll(node, amount) {
if (node === window) {
((/** @type {?} */ (node))).scrollBy(amount, 0);
}
else {
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
((/** @type {?} */ (node))).scrollLeft += amount;
}
}
/**
* Gets whether the vertical auto-scroll direction of a node.
* @param {?} clientRect Dimensions of the node.
* @param {?} pointerY Position of the user's pointer along the y axis.
* @return {?}
*/
function getVerticalScrollDirection(clientRect, pointerY) {
const { top, bottom, height } = clientRect;
/** @type {?} */
const yThreshold = height * SCROLL_PROXIMITY_THRESHOLD;
if (pointerY >= top - yThreshold && pointerY <= top + yThreshold) {
return 1 /* UP */;
}
else if (pointerY >= bottom - yThreshold && pointerY <= bottom + yThreshold) {
return 2 /* DOWN */;
}
return 0 /* NONE */;
}
/**
* Gets whether the horizontal auto-scroll direction of a node.
* @param {?} clientRect Dimensions of the node.
* @param {?} pointerX Position of the user's pointer along the x axis.
* @return {?}
*/
function getHorizontalScrollDirection(clientRect, pointerX) {
const { left, right, width } = clientRect;
/** @type {?} */
const xThreshold = width * SCROLL_PROXIMITY_THRESHOLD;
if (pointerX >= left - xThreshold && pointerX <= left + xThreshold) {
return 1 /* LEFT */;
}
else if (pointerX >= right - xThreshold && pointerX <= right + xThreshold) {
return 2 /* RIGHT */;
}
return 0 /* NONE */;
}
/**
* Gets the directions in which an element node should be scrolled,
* assuming that the user's pointer is already within it scrollable region.
* @param {?} element Element for which we should calculate the scroll direction.
* @param {?} clientRect Bounding client rectangle of the element.
* @param {?} pointerX Position of the user's pointer along the x axis.
* @param {?} pointerY Position of the user's pointer along the y axis.
* @return {?}
*/
function getElementScrollDirections(element, clientRect, pointerX, pointerY) {
/** @type {?} */
const computedVertical = getVerticalScrollDirection(clientRect, pointerY);
/** @type {?} */
const computedHorizontal = getHorizontalScrollDirection(clientRect, pointerX);
/** @type {?} */
let verticalScrollDirection = 0 /* NONE */;
/** @type {?} */
let horizontalScrollDirection = 0 /* NONE */;
// Note that we here we do some extra checks for whether the element is actually scrollable in
// a certain direction and we only assign the scroll direction if it is. We do this so that we
// can allow other elements to be scrolled, if the current element can't be scrolled anymore.
// This allows us to handle cases where the scroll regions of two scrollable elements overlap.
if (computedVertical) {
/** @type {?} */
const scrollTop = element.scrollTop;
if (computedVertical === 1 /* UP */) {
if (scrollTop > 0) {
verticalScrollDirection = 1 /* UP */;
}
}
else if (element.scrollHeight - scrollTop > element.clientHeight) {
verticalScrollDirection = 2 /* DOWN */;
}
}
if (computedHorizontal) {
/** @type {?} */
const scrollLeft = element.scrollLeft;
if (computedHorizontal === 1 /* LE