UNPKG

@angular/cdk

Version:

Angular Material Component Development Kit

893 lines 131 kB
/** * @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 { __read, __spread } from "tslib"; 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. */ var 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. */ var 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. */ var AUTO_SCROLL_STEP = 2; /** * Reference to a drop list. Used to manipulate or dispose of the container. */ var DropListRef = /** @class */ (function () { function DropListRef(element, _dragDropRegistry, _document, _ngZone, _viewportRuler) { var _this = this; 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 = function () { 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 = []; /** Cached positions of the scrollable parent elements. */ this._parentPositions = new Map(); /** * 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: 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; /** Starts the interval that'll auto-scroll the element. */ this._startScrollInterval = function () { _this._stopScrolling(); interval(0, animationFrameScheduler) .pipe(takeUntil(_this._stopScrollTimers)) .subscribe(function () { var 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; this.withScrollableParents([this.element]); _dragDropRegistry.registerDropContainer(this); } /** Removes the drop list functionality from the DOM element. */ DropListRef.prototype.dispose = function () { this._stopScrolling(); this._stopScrollTimers.complete(); this._viewportScrollSubscription.unsubscribe(); this.beforeStarted.complete(); this.entered.complete(); this.exited.complete(); this.dropped.complete(); this.sorted.complete(); this._activeSiblings.clear(); this._scrollNode = null; this._parentPositions.clear(); this._dragDropRegistry.removeDropContainer(this); }; /** Whether an item from this list is currently being dragged. */ DropListRef.prototype.isDragging = function () { return this._isDragging; }; /** Starts dragging an item. */ DropListRef.prototype.start = function () { var _this = this; var styles = coerceElement(this.element).style; this.beforeStarted.next(); this._isDragging = true; // We need to disable scroll snapping while the user is dragging, because it breaks automatic // scrolling. The browser seems to round the value based on the snapping points which means // that we can't increment/decrement the scroll position. this._initialScrollSnap = styles.msScrollSnapType || styles.scrollSnapType || ''; styles.scrollSnapType = styles.msScrollSnapType = 'none'; this._cacheItems(); this._siblings.forEach(function (sibling) { return sibling._startReceiving(_this); }); this._viewportScrollSubscription.unsubscribe(); 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. * @param index Index at which the item entered. If omitted, the container will try to figure it * out automatically. */ DropListRef.prototype.enter = function (item, pointerX, pointerY, index) { 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. var newIndex; if (index == null) { 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); } } else { newIndex = index; } var activeDraggables = this._activeDraggables; var currentIndex = activeDraggables.indexOf(item); var placeholder = item.getPlaceholderElement(); var 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)) { var element = newPositionReference.getRootElement(); 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: 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. */ DropListRef.prototype.exit = function (item) { this._reset(); this.exited.next({ item: 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. * @param previousIndex Index of the item when dragging started. * * @breaking-change 11.0.0 `previousIndex` parameter to become required. */ DropListRef.prototype.drop = function (item, currentIndex, previousContainer, isPointerOverContainer, distance, previousIndex) { this._reset(); // @breaking-change 11.0.0 Remove this fallback logic once `previousIndex` is a required param. if (previousIndex == null) { previousIndex = previousContainer.getItemIndex(item); } this.dropped.next({ item: item, currentIndex: currentIndex, previousIndex: previousIndex, container: this, previousContainer: previousContainer, isPointerOverContainer: isPointerOverContainer, distance: distance }); }; /** * Sets the draggable items that are a part of this list. * @param items Items that are a part of this list. */ DropListRef.prototype.withItems = function (items) { var _this = this; this._draggables = items; items.forEach(function (item) { return item._withDropContainer(_this); }); if (this.isDragging()) { this._cacheItems(); } return this; }; /** Sets the layout direction of the drop list. */ DropListRef.prototype.withDirection = function (direction) { this._direction = direction; return 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. * @param connectedTo Other containers that the current containers should be connected to. */ DropListRef.prototype.connectedTo = function (connectedTo) { this._siblings = connectedTo.slice(); return this; }; /** * Sets the orientation of the container. * @param orientation New orientation for the container. */ DropListRef.prototype.withOrientation = function (orientation) { this._orientation = orientation; return this; }; /** * Sets which parent elements are can be scrolled while the user is dragging. * @param elements Elements that can be scrolled. */ DropListRef.prototype.withScrollableParents = function (elements) { var element = coerceElement(this.element); // We always allow the current element to be scrollable // so we need to ensure that it's in the array. this._scrollableElements = elements.indexOf(element) === -1 ? __spread([element], elements) : elements.slice(); return this; }; /** * Figures out the index of an item in the container. * @param item Item whose index should be determined. */ DropListRef.prototype.getItemIndex = function (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. var items = this._orientation === 'horizontal' && this._direction === 'rtl' ? this._itemPositions.slice().reverse() : this._itemPositions; return findIndex(items, function (currentItem) { return currentItem.drag === item; }); }; /** * Whether the list is able to receive the item that * is currently being dragged inside a connected drop list. */ DropListRef.prototype.isReceiving = function () { 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. */ DropListRef.prototype._sortItem = function (item, pointerX, pointerY, pointerDelta) { // Don't sort the item if sorting is disabled or it's out of range. if (this.sortingDisabled || !isPointerNearClientRect(this._clientRect, pointerX, pointerY)) { return; } var siblings = this._itemPositions; var newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta); if (newIndex === -1 && siblings.length > 0) { return; } var isHorizontal = this._orientation === 'horizontal'; var currentIndex = findIndex(siblings, function (currentItem) { return currentItem.drag === item; }); var siblingAtNewPosition = siblings[newIndex]; var currentPosition = siblings[currentIndex].clientRect; var newPosition = siblingAtNewPosition.clientRect; var 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. var itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta); // How many pixels all the other items should be offset. var 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. var oldOrder = siblings.slice(); // Shuffle the array in place. moveItemInArray(siblings, currentIndex, newIndex); this.sorted.next({ previousIndex: currentIndex, currentIndex: newIndex, container: this, item: item }); siblings.forEach(function (sibling, index) { // Don't do anything if the position hasn't changed. if (oldOrder[index] === sibling) { return; } var isDraggedItem = sibling.drag === item; var offset = isDraggedItem ? itemOffset : siblingOffset; var 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. */ DropListRef.prototype._startScrollingIfNecessary = function (pointerX, pointerY) { var _this = this; if (this.autoScrollDisabled) { return; } var scrollNode; var verticalScrollDirection = 0 /* NONE */; var horizontalScrollDirection = 0 /* NONE */; // Check whether we should start scrolling any of the parent containers. this._parentPositions.forEach(function (position, element) { var _a; // We have special handling for the `document` below. Also this would be // nicer with a for...of loop, but it requires changing a compiler flag. if (element === _this._document || !position.clientRect || scrollNode) { return; } if (isPointerNearClientRect(position.clientRect, pointerX, pointerY)) { _a = __read(getElementScrollDirections(element, position.clientRect, pointerX, pointerY), 2), verticalScrollDirection = _a[0], horizontalScrollDirection = _a[1]; if (verticalScrollDirection || horizontalScrollDirection) { scrollNode = element; } } }); // Otherwise check if we can start scrolling the viewport. if (!verticalScrollDirection && !horizontalScrollDirection) { var _a = this._viewportRuler.getViewportSize(), width = _a.width, height = _a.height; var clientRect = { width: width, height: 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. */ DropListRef.prototype._stopScrolling = function () { this._stopScrollTimers.next(); }; /** Caches the positions of the configured scrollable parents. */ DropListRef.prototype._cacheParentPositions = function () { var _this = this; this._parentPositions.clear(); this._parentPositions.set(this._document, { scrollPosition: this._viewportRuler.getViewportScrollPosition(), }); this._scrollableElements.forEach(function (element) { var clientRect = getMutableClientRect(element); // We keep the ClientRect cached in two properties, because it's referenced in a lot of // performance-sensitive places and we want to avoid the extra lookups. The `element` is // guaranteed to always be in the `_scrollableElements` so this should always match. if (element === _this.element) { _this._clientRect = clientRect; } _this._parentPositions.set(element, { scrollPosition: { top: element.scrollTop, left: element.scrollLeft }, clientRect: clientRect }); }); }; /** Refreshes the position cache of the items and sibling containers. */ DropListRef.prototype._cacheItemPositions = function () { var isHorizontal = this._orientation === 'horizontal'; this._itemPositions = this._activeDraggables.map(function (drag) { var elementToMeasure = drag.getVisibleElement(); return { drag: drag, offset: 0, clientRect: getMutableClientRect(elementToMeasure) }; }).sort(function (a, b) { return isHorizontal ? a.clientRect.left - b.clientRect.left : a.clientRect.top - b.clientRect.top; }); }; /** Resets the container to its initial state. */ DropListRef.prototype._reset = function () { var _this = this; this._isDragging = false; var styles = coerceElement(this.element).style; styles.scrollSnapType = styles.msScrollSnapType = this._initialScrollSnap; // TODO(crisbeto): may have to wait for the animations to finish. this._activeDraggables.forEach(function (item) { return item.getRootElement().style.transform = ''; }); this._siblings.forEach(function (sibling) { return sibling._stopReceiving(_this); }); this._activeDraggables = []; this._itemPositions = []; this._previousSwap.drag = null; this._previousSwap.delta = 0; this._stopScrolling(); this._viewportScrollSubscription.unsubscribe(); this._parentPositions.clear(); }; /** * Gets the offset in pixels by which the items that aren't being dragged should be moved. * @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. */ DropListRef.prototype._getSiblingOffsetPx = function (currentIndex, siblings, delta) { var isHorizontal = this._orientation === 'horizontal'; var currentPosition = siblings[currentIndex].clientRect; var immediateSibling = siblings[currentIndex + delta * -1]; var siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta; if (immediateSibling) { var start = isHorizontal ? 'left' : 'top'; var 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; }; /** * Gets the offset in pixels by which the item that is being dragged should be moved. * @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. */ DropListRef.prototype._getItemOffsetPx = function (currentPosition, newPosition, delta) { var isHorizontal = this._orientation === 'horizontal'; var 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. * @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. */ DropListRef.prototype._getItemIndexFromPointerPosition = function (item, pointerX, pointerY, delta) { var _this = this; var isHorizontal = this._orientation === 'horizontal'; return findIndex(this._itemPositions, function (_a, _, array) { var drag = _a.drag, clientRect = _a.clientRect; 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) { var 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. */ DropListRef.prototype._cacheItems = function () { this._activeDraggables = this._draggables.slice(); this._cacheItemPositions(); this._cacheParentPositions(); }; /** * Updates the internal state of the container after a scroll event has happened. * @param scrolledParent Element that was scrolled. * @param newTop New top scroll position. * @param newLeft New left scroll position. */ DropListRef.prototype._updateAfterScroll = function (scrolledParent, newTop, newLeft) { var _this = this; var scrollPosition = this._parentPositions.get(scrolledParent).scrollPosition; var topDifference = scrollPosition.top - newTop; var leftDifference = scrollPosition.left - newLeft; // Go through and update the cached positions of the scroll // parents that are inside the element that was scrolled. this._parentPositions.forEach(function (position, node) { if (position.clientRect && scrolledParent !== node && scrolledParent.contains(node)) { adjustClientRect(position.clientRect, 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(function (_a) { var clientRect = _a.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(function (_a) { var drag = _a.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; }; /** * 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. */ DropListRef.prototype._isOverContainer = function (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. */ DropListRef.prototype._getSiblingContainerFromPosition = function (item, x, y) { return this._siblings.find(function (sibling) { return 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. */ DropListRef.prototype._canReceive = function (item, x, y) { if (!isInsideClientRect(this._clientRect, x, y) || !this.enterPredicate(item, this)) { return false; } var elementFromPoint = 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; } var 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. */ DropListRef.prototype._startReceiving = function (sibling) { var activeSiblings = this._activeSiblings; if (!activeSiblings.has(sibling)) { activeSiblings.add(sibling); this._cacheParentPositions(); this._listenToScrollEvents(); } }; /** * Called by a connected drop list when dragging has stopped. * @param sibling Sibling whose dragging has stopped. */ DropListRef.prototype._stopReceiving = function (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. */ DropListRef.prototype._listenToScrollEvents = function () { var _this = this; this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe(function (event) { if (_this.isDragging()) { var target = event.target; var position = _this._parentPositions.get(target); if (position) { var newTop = void 0; var newLeft = void 0; if (target === _this._document) { var scrollPosition = _this._viewportRuler.getViewportScrollPosition(); newTop = scrollPosition.top; newLeft = scrollPosition.left; } else { newTop = target.scrollTop; newLeft = target.scrollLeft; } _this._updateAfterScroll(target, newTop, newLeft); } } else if (_this.isReceiving()) { _this._cacheParentPositions(); } }); }; /** * 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`. */ DropListRef.prototype._getShadowRoot = function () { if (!this._cachedShadowRoot) { this._cachedShadowRoot = getShadowRoot(coerceElement(this.element)) || this._document; } return this._cachedShadowRoot; }; return DropListRef; }()); export { DropListRef }; /** * 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. */ function adjustClientRect(clientRect, top, left) { clientRect.top += top; clientRect.bottom = clientRect.top + clientRect.height; clientRect.left += left; clientRect.right = clientRect.left + clientRect.width; } /** * Checks whether the pointer coordinates are close to a ClientRect. * @param rect ClientRect to check against. * @param pointerX Coordinates along the X axis. * @param pointerY Coordinates along the Y axis. */ function isPointerNearClientRect(rect, pointerX, pointerY) { var top = rect.top, right = rect.right, bottom = rect.bottom, left = rect.left, width = rect.width, height = rect.height; var xThreshold = width * DROP_PROXIMITY_THRESHOLD; var yThreshold = height * DROP_PROXIMITY_THRESHOLD; return pointerY > top - yThreshold && pointerY < bottom + yThreshold && pointerX > left - xThreshold && pointerX < right + xThreshold; } /** * 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. * @param array Array in which to look for matches. * @param predicate Function used to determine whether an item is a match. */ function findIndex(array, predicate) { for (var 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. */ function isInsideClientRect(clientRect, x, y) { var top = clientRect.top, bottom = clientRect.bottom, left = clientRect.left, right = clientRect.right; return y >= top && y <= bottom && x >= left && x <= right; } /** Gets a mutable version of an element's bounding `ClientRect`. */ function getMutableClientRect(element) { var 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. */ function incrementVerticalScroll(node, amount) { if (node === window) { node.scrollBy(0, amount); } else { // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it. 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. */ function incrementHorizontalScroll(node, amount) { if (node === window) { node.scrollBy(amount, 0); } else { // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it. 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. */ function getVerticalScrollDirection(clientRect, pointerY) { var top = clientRect.top, bottom = clientRect.bottom, height = clientRect.height; var 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. */ function getHorizontalScrollDirection(clientRect, pointerX) { var left = clientRect.left, right = clientRect.right, width = clientRect.width; var 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. */ function getElementScrollDirections(element, clientRect, pointerX, pointerY) { var computedVertical = getVerticalScrollDirection(clientRect, pointerY); var computedHorizontal = getHorizontalScrollDirection(clientRect, pointerX); var verticalScrollDirection = 0 /* NONE */; var 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) { var 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) { var scrollLeft = element.scrollLeft; if (computedHorizontal === 1 /* LEFT */) { if (scrollLeft > 0) { horizontalScrollDirection = 1 /* LEFT */; } } else if (element.scrollWidth - scrollLeft > element.clientWidth) { horizontalScrollDirection = 2 /* RIGHT */; } } return [verticalScrollDirection, horizontalScrollDirection]; } /** Gets the shadow root of an element, if any. */ function getShadowRoot(element) { if (_supportsShadowDom()) { var rootNode = element.getRootNode ? element.getRootNode() : null; if (rootNode instanceof ShadowRoot) { return rootNode; } } return null; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJvcC1saXN0LXJlZi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9jZGsvZHJhZy1kcm9wL2Ryb3AtbGlzdC1yZWYudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HOztBQUlILE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSx1QkFBdUIsQ0FBQztBQUVwRCxPQUFPLEVBQUMsa0JBQWtCLEVBQUMsTUFBTSx1QkFBdUIsQ0FBQztBQUN6RCxPQUFPLEVBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsdUJBQXVCLEVBQUMsTUFBTSxNQUFNLENBQUM7QUFDOUUsT0FBTyxFQUFDLFNBQVMsRUFBQyxNQUFNLGdCQUFnQixDQUFDO0FBQ3pDLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxjQUFjLENBQUM7QUFJN0M7OztHQUdHO0FBQ0gsSUFBTSx3QkFBd0IsR0FBRyxJQUFJLENBQUM7QUFFdEM7OztHQUdHO0FBQ0gsSUFBTSwwQkFBMEIsR0FBRyxJQUFJLENBQUM7QUFFeEM7OztHQUdHO0FBQ0gsSUFBTSxnQkFBZ0IsR0FBRyxDQUFDLENBQUM7QUFrQzNCOztHQUVHO0FBQ0g7SUFtSUUscUJBQ0UsT0FBOEMsRUFDdEMsaUJBQXlELEVBQ2pFLFNBQWMsRUFDTixPQUFlLEVBQ2YsY0FBNkI7UUFMdkMsaUJBVUM7UUFSUyxzQkFBaUIsR0FBakIsaUJBQWlCLENBQXdDO1FBRXpELFlBQU8sR0FBUCxPQUFPLENBQVE7UUFDZixtQkFBYyxHQUFkLGNBQWMsQ0FBZTtRQXBJdkMsNEVBQTRFO1FBQzVFLGFBQVEsR0FBWSxLQUFLLENBQUM7UUFFMUIseURBQXlEO1FBQ3pELG9CQUFlLEdBQVksS0FBSyxDQUFDO1FBS2pDOzs7V0FHRztRQUNILHVCQUFrQixHQUFZLEtBQUssQ0FBQztRQUVwQzs7O1dBR0c7UUFDSCxtQkFBYyxHQUFrRCxjQUFNLE9BQUEsSUFBSSxFQUFKLENB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