| /** |
| * @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 { normalizePassiveListenerOptions } from '@angular/cdk/platform'; |
| import { coerceBooleanProperty, coerceElement, coerceNumberProperty, coerceArray } from '@angular/cdk/coercion'; |
| import { Subscription, Subject, interval, animationFrameScheduler, Observable, merge } from 'rxjs'; |
| import { startWith, takeUntil, take, map, switchMap, tap } from 'rxjs/operators'; |
| import { ElementRef, Injectable, NgZone, Inject, InjectionToken, NgModule, ContentChildren, EventEmitter, forwardRef, Input, Output, Optional, Directive, ChangeDetectorRef, SkipSelf, ContentChild, ViewContainerRef, isDevMode, TemplateRef, ɵɵdefineInjectable, ɵɵinject } from '@angular/core'; |
| import { DOCUMENT } from '@angular/common'; |
| import { ViewportRuler } from '@angular/cdk/scrolling'; |
| import { Directionality } from '@angular/cdk/bidi'; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Shallow-extends a stylesheet object with another stylesheet object. |
| * \@docs-private |
| * @param {?} dest |
| * @param {?} source |
| * @return {?} |
| */ |
| function extendStyles(dest, source) { |
| for (let key in source) { |
| if (source.hasOwnProperty(key)) { |
| dest[(/** @type {?} */ (key))] = source[(/** @type {?} */ (key))]; |
| } |
| } |
| return dest; |
| } |
| /** |
| * Toggles whether the native drag interactions should be enabled for an element. |
| * \@docs-private |
| * @param {?} element Element on which to toggle the drag interactions. |
| * @param {?} enable Whether the drag interactions should be enabled. |
| * @return {?} |
| */ |
| function toggleNativeDragInteractions(element, enable) { |
| /** @type {?} */ |
| const userSelect = enable ? '' : 'none'; |
| extendStyles(element.style, { |
| touchAction: enable ? '' : 'none', |
| webkitUserDrag: enable ? '' : 'none', |
| webkitTapHighlightColor: enable ? '' : 'transparent', |
| userSelect: userSelect, |
| msUserSelect: userSelect, |
| webkitUserSelect: userSelect, |
| MozUserSelect: userSelect |
| }); |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * Parses a CSS time value to milliseconds. |
| * @param {?} value |
| * @return {?} |
| */ |
| function parseCssTimeUnitsToMs(value) { |
| // Some browsers will return it in seconds, whereas others will return milliseconds. |
| /** @type {?} */ |
| const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000; |
| return parseFloat(value) * multiplier; |
| } |
| /** |
| * Gets the transform transition duration, including the delay, of an element in milliseconds. |
| * @param {?} element |
| * @return {?} |
| */ |
| function getTransformTransitionDurationInMs(element) { |
| /** @type {?} */ |
| const computedStyle = getComputedStyle(element); |
| /** @type {?} */ |
| const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property'); |
| /** @type {?} */ |
| const property = transitionedProperties.find((/** |
| * @param {?} prop |
| * @return {?} |
| */ |
| prop => prop === 'transform' || prop === 'all')); |
| // If there's no transition for `all` or `transform`, we shouldn't do anything. |
| if (!property) { |
| return 0; |
| } |
| // Get the index of the property that we're interested in and match |
| // it up to the same index in `transition-delay` and `transition-duration`. |
| /** @type {?} */ |
| const propertyIndex = transitionedProperties.indexOf(property); |
| /** @type {?} */ |
| const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration'); |
| /** @type {?} */ |
| const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay'); |
| return parseCssTimeUnitsToMs(rawDurations[propertyIndex]) + |
| parseCssTimeUnitsToMs(rawDelays[propertyIndex]); |
| } |
| /** |
| * Parses out multiple values from a computed style into an array. |
| * @param {?} computedStyle |
| * @param {?} name |
| * @return {?} |
| */ |
| function parseCssPropertyValue(computedStyle, name) { |
| /** @type {?} */ |
| const value = computedStyle.getPropertyValue(name); |
| return value.split(',').map((/** |
| * @param {?} part |
| * @return {?} |
| */ |
| part => part.trim())); |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Options that can be used to bind a passive event listener. |
| * @type {?} |
| */ |
| const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true }); |
| /** |
| * Options that can be used to bind an active event listener. |
| * @type {?} |
| */ |
| const activeEventListenerOptions = normalizePassiveListenerOptions({ passive: false }); |
| /** |
| * Time in milliseconds for which to ignore mouse events, after |
| * receiving a touch event. Used to avoid doing double work for |
| * touch devices where the browser fires fake mouse events, in |
| * addition to touch events. |
| * @type {?} |
| */ |
| const MOUSE_EVENT_IGNORE_TIME = 800; |
| /** |
| * Reference to a draggable item. Used to manipulate or dispose of the item. |
| * \@docs-private |
| * @template T |
| */ |
| class DragRef { |
| /** |
| * @param {?} element |
| * @param {?} _config |
| * @param {?} _document |
| * @param {?} _ngZone |
| * @param {?} _viewportRuler |
| * @param {?} _dragDropRegistry |
| */ |
| constructor(element, _config, _document, _ngZone, _viewportRuler, _dragDropRegistry) { |
| this._config = _config; |
| this._document = _document; |
| this._ngZone = _ngZone; |
| this._viewportRuler = _viewportRuler; |
| this._dragDropRegistry = _dragDropRegistry; |
| /** |
| * CSS `transform` applied to the element when it isn't being dragged. We need a |
| * passive transform in order for the dragged element to retain its new position |
| * after the user has stopped dragging and because we need to know the relative |
| * position in case they start dragging again. This corresponds to `element.style.transform`. |
| */ |
| this._passiveTransform = { x: 0, y: 0 }; |
| /** |
| * CSS `transform` that is applied to the element while it's being dragged. |
| */ |
| this._activeTransform = { x: 0, y: 0 }; |
| /** |
| * Emits when the item is being moved. |
| */ |
| this._moveEvents = new Subject(); |
| /** |
| * Subscription to pointer movement events. |
| */ |
| this._pointerMoveSubscription = Subscription.EMPTY; |
| /** |
| * Subscription to the event that is dispatched when the user lifts their pointer. |
| */ |
| this._pointerUpSubscription = Subscription.EMPTY; |
| /** |
| * Subscription to the viewport being scrolled. |
| */ |
| this._scrollSubscription = Subscription.EMPTY; |
| /** |
| * Cached reference to the boundary element. |
| */ |
| this._boundaryElement = null; |
| /** |
| * Whether the native dragging interactions have been enabled on the root element. |
| */ |
| this._nativeInteractionsEnabled = true; |
| /** |
| * Elements that can be used to drag the draggable item. |
| */ |
| this._handles = []; |
| /** |
| * Registered handles that are currently disabled. |
| */ |
| this._disabledHandles = new Set(); |
| /** |
| * Layout direction of the item. |
| */ |
| this._direction = 'ltr'; |
| /** |
| * Amount of milliseconds to wait after the user has put their |
| * pointer down before starting to drag the element. |
| */ |
| this.dragStartDelay = 0; |
| this._disabled = false; |
| /** |
| * Emits as the drag sequence is being prepared. |
| */ |
| this.beforeStarted = new Subject(); |
| /** |
| * Emits when the user starts dragging the item. |
| */ |
| this.started = new Subject(); |
| /** |
| * Emits when the user has released a drag item, before any animations have started. |
| */ |
| this.released = new Subject(); |
| /** |
| * Emits when the user stops dragging an item in the container. |
| */ |
| this.ended = new Subject(); |
| /** |
| * Emits when the user has moved the item into a new container. |
| */ |
| this.entered = new Subject(); |
| /** |
| * Emits when the user removes the item its container by dragging it into another container. |
| */ |
| this.exited = new Subject(); |
| /** |
| * Emits when the user drops the item inside a container. |
| */ |
| this.dropped = new Subject(); |
| /** |
| * Emits as the user is dragging the item. Use with caution, |
| * because this event will fire for every pixel that the user has dragged. |
| */ |
| this.moved = this._moveEvents.asObservable(); |
| /** |
| * Handler for the `mousedown`/`touchstart` events. |
| */ |
| this._pointerDown = (/** |
| * @param {?} event |
| * @return {?} |
| */ |
| (event) => { |
| this.beforeStarted.next(); |
| // Delegate the event based on whether it started from a handle or the element itself. |
| if (this._handles.length) { |
| /** @type {?} */ |
| const targetHandle = this._handles.find((/** |
| * @param {?} handle |
| * @return {?} |
| */ |
| handle => { |
| /** @type {?} */ |
| const target = event.target; |
| return !!target && (target === handle || handle.contains((/** @type {?} */ (target)))); |
| })); |
| if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) { |
| this._initializeDragSequence(targetHandle, event); |
| } |
| } |
| else if (!this.disabled) { |
| this._initializeDragSequence(this._rootElement, event); |
| } |
| }); |
| /** |
| * Handler that is invoked when the user moves their pointer after they've initiated a drag. |
| */ |
| this._pointerMove = (/** |
| * @param {?} event |
| * @return {?} |
| */ |
| (event) => { |
| if (!this._hasStartedDragging) { |
| /** @type {?} */ |
| const pointerPosition = this._getPointerPositionOnPage(event); |
| /** @type {?} */ |
| const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x); |
| /** @type {?} */ |
| const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y); |
| /** @type {?} */ |
| const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold; |
| // Only start dragging after the user has moved more than the minimum distance in either |
| // direction. Note that this is preferrable over doing something like `skip(minimumDistance)` |
| // in the `pointerMove` subscription, because we're not guaranteed to have one move event |
| // per pixel of movement (e.g. if the user moves their pointer quickly). |
| if (isOverThreshold) { |
| /** @type {?} */ |
| const isDelayElapsed = Date.now() >= this._dragStartTime + (this.dragStartDelay || 0); |
| if (!isDelayElapsed) { |
| this._endDragSequence(event); |
| return; |
| } |
| // Prevent other drag sequences from starting while something in the container is still |
| // being dragged. This can happen while we're waiting for the drop animation to finish |
| // and can cause errors, because some elements might still be moving around. |
| if (!this._dropContainer || !this._dropContainer.isDragging()) { |
| this._hasStartedDragging = true; |
| this._ngZone.run((/** |
| * @return {?} |
| */ |
| () => this._startDragSequence(event))); |
| } |
| } |
| return; |
| } |
| // We only need the preview dimensions if we have a boundary element. |
| if (this._boundaryElement) { |
| // Cache the preview element rect if we haven't cached it already or if |
| // we cached it too early before the element dimensions were computed. |
| if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) { |
| this._previewRect = (this._preview || this._rootElement).getBoundingClientRect(); |
| } |
| } |
| /** @type {?} */ |
| const constrainedPointerPosition = this._getConstrainedPointerPosition(event); |
| this._hasMoved = true; |
| event.preventDefault(); |
| this._updatePointerDirectionDelta(constrainedPointerPosition); |
| if (this._dropContainer) { |
| this._updateActiveDropContainer(constrainedPointerPosition); |
| } |
| else { |
| /** @type {?} */ |
| const activeTransform = this._activeTransform; |
| activeTransform.x = |
| constrainedPointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x; |
| activeTransform.y = |
| constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y; |
| this._applyRootElementTransform(activeTransform.x, activeTransform.y); |
| // Apply transform as attribute if dragging and svg element to work for IE |
| if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) { |
| /** @type {?} */ |
| const appliedTransform = `translate(${activeTransform.x} ${activeTransform.y})`; |
| this._rootElement.setAttribute('transform', appliedTransform); |
| } |
| } |
| // Since this event gets fired for every pixel while dragging, we only |
| // want to fire it if the consumer opted into it. Also we have to |
| // re-enter the zone because we run all of the events on the outside. |
| if (this._moveEvents.observers.length) { |
| this._ngZone.run((/** |
| * @return {?} |
| */ |
| () => { |
| this._moveEvents.next({ |
| source: this, |
| pointerPosition: constrainedPointerPosition, |
| event, |
| distance: this._getDragDistance(constrainedPointerPosition), |
| delta: this._pointerDirectionDelta |
| }); |
| })); |
| } |
| }); |
| /** |
| * Handler that is invoked when the user lifts their pointer up, after initiating a drag. |
| */ |
| this._pointerUp = (/** |
| * @param {?} event |
| * @return {?} |
| */ |
| (event) => { |
| this._endDragSequence(event); |
| }); |
| this.withRootElement(element); |
| _dragDropRegistry.registerDragItem(this); |
| } |
| /** |
| * Whether starting to drag this element is disabled. |
| * @return {?} |
| */ |
| get disabled() { |
| return this._disabled || !!(this._dropContainer && this._dropContainer.disabled); |
| } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set disabled(value) { |
| /** @type {?} */ |
| const newValue = coerceBooleanProperty(value); |
| if (newValue !== this._disabled) { |
| this._disabled = newValue; |
| this._toggleNativeDragInteractions(); |
| } |
| } |
| /** |
| * Returns the element that is being used as a placeholder |
| * while the current element is being dragged. |
| * @return {?} |
| */ |
| getPlaceholderElement() { |
| return this._placeholder; |
| } |
| /** |
| * Returns the root draggable element. |
| * @return {?} |
| */ |
| getRootElement() { |
| return this._rootElement; |
| } |
| /** |
| * Registers the handles that can be used to drag the element. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} handles |
| * @return {THIS} |
| */ |
| withHandles(handles) { |
| (/** @type {?} */ (this))._handles = handles.map((/** |
| * @param {?} handle |
| * @return {?} |
| */ |
| handle => coerceElement(handle))); |
| (/** @type {?} */ (this))._handles.forEach((/** |
| * @param {?} handle |
| * @return {?} |
| */ |
| handle => toggleNativeDragInteractions(handle, false))); |
| (/** @type {?} */ (this))._toggleNativeDragInteractions(); |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Registers the template that should be used for the drag preview. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} template Template that from which to stamp out the preview. |
| * @return {THIS} |
| */ |
| withPreviewTemplate(template) { |
| (/** @type {?} */ (this))._previewTemplate = template; |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Registers the template that should be used for the drag placeholder. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} template Template that from which to stamp out the placeholder. |
| * @return {THIS} |
| */ |
| withPlaceholderTemplate(template) { |
| (/** @type {?} */ (this))._placeholderTemplate = template; |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Sets an alternate drag root element. The root element is the element that will be moved as |
| * the user is dragging. Passing an alternate root element is useful when trying to enable |
| * dragging on an element that you might not have access to. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} rootElement |
| * @return {THIS} |
| */ |
| withRootElement(rootElement) { |
| /** @type {?} */ |
| const element = coerceElement(rootElement); |
| if (element !== (/** @type {?} */ (this))._rootElement) { |
| if ((/** @type {?} */ (this))._rootElement) { |
| (/** @type {?} */ (this))._removeRootElementListeners((/** @type {?} */ (this))._rootElement); |
| } |
| element.addEventListener('mousedown', (/** @type {?} */ (this))._pointerDown, activeEventListenerOptions); |
| element.addEventListener('touchstart', (/** @type {?} */ (this))._pointerDown, passiveEventListenerOptions); |
| (/** @type {?} */ (this))._initialTransform = undefined; |
| (/** @type {?} */ (this))._rootElement = element; |
| } |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Element to which the draggable's position will be constrained. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} boundaryElement |
| * @return {THIS} |
| */ |
| withBoundaryElement(boundaryElement) { |
| (/** @type {?} */ (this))._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null; |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Removes the dragging functionality from the DOM element. |
| * @return {?} |
| */ |
| dispose() { |
| this._removeRootElementListeners(this._rootElement); |
| // Do this check before removing from the registry since it'll |
| // stop being considered as dragged once it is removed. |
| if (this.isDragging()) { |
| // Since we move out the element to the end of the body while it's being |
| // dragged, we have to make sure that it's removed if it gets destroyed. |
| removeElement(this._rootElement); |
| } |
| this._destroyPreview(); |
| this._destroyPlaceholder(); |
| this._dragDropRegistry.removeDragItem(this); |
| this._removeSubscriptions(); |
| this.beforeStarted.complete(); |
| this.started.complete(); |
| this.released.complete(); |
| this.ended.complete(); |
| this.entered.complete(); |
| this.exited.complete(); |
| this.dropped.complete(); |
| this._moveEvents.complete(); |
| this._handles = []; |
| this._disabledHandles.clear(); |
| this._dropContainer = undefined; |
| this._boundaryElement = this._rootElement = this._placeholderTemplate = |
| this._previewTemplate = this._nextSibling = (/** @type {?} */ (null)); |
| } |
| /** |
| * Checks whether the element is currently being dragged. |
| * @return {?} |
| */ |
| isDragging() { |
| return this._hasStartedDragging && this._dragDropRegistry.isDragging(this); |
| } |
| /** |
| * Resets a standalone drag item to its initial position. |
| * @return {?} |
| */ |
| reset() { |
| this._rootElement.style.transform = this._initialTransform || ''; |
| this._activeTransform = { x: 0, y: 0 }; |
| this._passiveTransform = { x: 0, y: 0 }; |
| } |
| /** |
| * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging. |
| * @param {?} handle Handle element that should be disabled. |
| * @return {?} |
| */ |
| disableHandle(handle) { |
| if (this._handles.indexOf(handle) > -1) { |
| this._disabledHandles.add(handle); |
| } |
| } |
| /** |
| * Enables a handle, if it has been disabled. |
| * @param {?} handle Handle element to be enabled. |
| * @return {?} |
| */ |
| enableHandle(handle) { |
| this._disabledHandles.delete(handle); |
| } |
| /** |
| * Sets the layout direction of the draggable item. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} direction |
| * @return {THIS} |
| */ |
| withDirection(direction) { |
| (/** @type {?} */ (this))._direction = direction; |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Sets the container that the item is part of. |
| * @param {?} container |
| * @return {?} |
| */ |
| _withDropContainer(container) { |
| this._dropContainer = container; |
| } |
| /** |
| * Gets the current position in pixels the draggable outside of a drop container. |
| * @return {?} |
| */ |
| getFreeDragPosition() { |
| /** @type {?} */ |
| const position = this.isDragging() ? this._activeTransform : this._passiveTransform; |
| return { x: position.x, y: position.y }; |
| } |
| /** |
| * Sets the current position in pixels the draggable outside of a drop container. |
| * @template THIS |
| * @this {THIS} |
| * @param {?} value New position to be set. |
| * @return {THIS} |
| */ |
| setFreeDragPosition(value) { |
| (/** @type {?} */ (this))._activeTransform = { x: 0, y: 0 }; |
| (/** @type {?} */ (this))._passiveTransform.x = value.x; |
| (/** @type {?} */ (this))._passiveTransform.y = value.y; |
| if (!(/** @type {?} */ (this))._dropContainer) { |
| (/** @type {?} */ (this))._applyRootElementTransform(value.x, value.y); |
| } |
| return (/** @type {?} */ (this)); |
| } |
| /** |
| * Updates the item's sort order based on the last-known pointer position. |
| * @return {?} |
| */ |
| _sortFromLastPointerPosition() { |
| /** @type {?} */ |
| const position = this._pointerPositionAtLastDirectionChange; |
| if (position && this._dropContainer) { |
| this._updateActiveDropContainer(position); |
| } |
| } |
| /** |
| * Unsubscribes from the global subscriptions. |
| * @private |
| * @return {?} |
| */ |
| _removeSubscriptions() { |
| this._pointerMoveSubscription.unsubscribe(); |
| this._pointerUpSubscription.unsubscribe(); |
| this._scrollSubscription.unsubscribe(); |
| } |
| /** |
| * Destroys the preview element and its ViewRef. |
| * @private |
| * @return {?} |
| */ |
| _destroyPreview() { |
| if (this._preview) { |
| removeElement(this._preview); |
| } |
| if (this._previewRef) { |
| this._previewRef.destroy(); |
| } |
| this._preview = this._previewRef = (/** @type {?} */ (null)); |
| } |
| /** |
| * Destroys the placeholder element and its ViewRef. |
| * @private |
| * @return {?} |
| */ |
| _destroyPlaceholder() { |
| if (this._placeholder) { |
| removeElement(this._placeholder); |
| } |
| if (this._placeholderRef) { |
| this._placeholderRef.destroy(); |
| } |
| this._placeholder = this._placeholderRef = (/** @type {?} */ (null)); |
| } |
| /** |
| * Clears subscriptions and stops the dragging sequence. |
| * @private |
| * @param {?} event Browser event object that ended the sequence. |
| * @return {?} |
| */ |
| _endDragSequence(event) { |
| // Note that here we use `isDragging` from the service, rather than from `this`. |
| // The difference is that the one from the service reflects whether a dragging sequence |
| // has been initiated, whereas the one on `this` includes whether the user has passed |
| // the minimum dragging threshold. |
| if (!this._dragDropRegistry.isDragging(this)) { |
| return; |
| } |
| this._removeSubscriptions(); |
| this._dragDropRegistry.stopDragging(this); |
| if (this._handles) { |
| this._rootElement.style.webkitTapHighlightColor = this._rootElementTapHighlight; |
| } |
| if (!this._hasStartedDragging) { |
| return; |
| } |
| this.released.next({ source: this }); |
| if (this._dropContainer) { |
| // Stop scrolling immediately, instead of waiting for the animation to finish. |
| this._dropContainer._stopScrolling(); |
| this._animatePreviewToPlaceholder().then((/** |
| * @return {?} |
| */ |
| () => { |
| this._cleanupDragArtifacts(event); |
| this._dragDropRegistry.stopDragging(this); |
| })); |
| } |
| else { |
| // 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. |
| this._passiveTransform.x = this._activeTransform.x; |
| this._passiveTransform.y = this._activeTransform.y; |
| this._ngZone.run((/** |
| * @return {?} |
| */ |
| () => { |
| this.ended.next({ |
| source: this, |
| distance: this._getDragDistance(this._getPointerPositionOnPage(event)) |
| }); |
| })); |
| this._dragDropRegistry.stopDragging(this); |
| } |
| } |
| /** |
| * Starts the dragging sequence. |
| * @private |
| * @param {?} event |
| * @return {?} |
| */ |
| _startDragSequence(event) { |
| // Emit the event on the item before the one on the container. |
| this.started.next({ source: this }); |
| if (isTouchEvent(event)) { |
| this._lastTouchEventTime = Date.now(); |
| } |
| this._toggleNativeDragInteractions(); |
| if (this._dropContainer) { |
| /** @type {?} */ |
| const element = this._rootElement; |
| // Grab the `nextSibling` before the preview and placeholder |
| // have been created so we don't get the preview by accident. |
| this._nextSibling = element.nextSibling; |
| /** @type {?} */ |
| const preview = this._preview = this._createPreviewElement(); |
| /** @type {?} */ |
| const placeholder = this._placeholder = this._createPlaceholderElement(); |
| // We move the element out at the end of the body and we make it hidden, because keeping it in |
| // place will throw off the consumer's `:last-child` selectors. We can't remove the element |
| // from the DOM completely, because iOS will stop firing all subsequent events in the chain. |
| element.style.display = 'none'; |
| this._document.body.appendChild((/** @type {?} */ (element.parentNode)).replaceChild(placeholder, element)); |
| getPreviewInsertionPoint(this._document).appendChild(preview); |
| this._dropContainer.start(); |
| } |
| } |
| /** |
| * Sets up the different variables and subscriptions |
| * that will be necessary for the dragging sequence. |
| * @private |
| * @param {?} referenceElement Element that started the drag sequence. |
| * @param {?} event Browser event object that started the sequence. |
| * @return {?} |
| */ |
| _initializeDragSequence(referenceElement, event) { |
| // Always stop propagation for the event that initializes |
| // the dragging sequence, in order to prevent it from potentially |
| // starting another sequence for a draggable parent somewhere up the DOM tree. |
| event.stopPropagation(); |
| /** @type {?} */ |
| const isDragging = this.isDragging(); |
| /** @type {?} */ |
| const isTouchSequence = isTouchEvent(event); |
| /** @type {?} */ |
| const isAuxiliaryMouseButton = !isTouchSequence && ((/** @type {?} */ (event))).button !== 0; |
| /** @type {?} */ |
| const rootElement = this._rootElement; |
| /** @type {?} */ |
| const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime && |
| this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now(); |
| // If the event started from an element with the native HTML drag&drop, it'll interfere |
| // with our own dragging (e.g. `img` tags do it by default). Prevent the default action |
| // to stop it from happening. Note that preventing on `dragstart` also seems to work, but |
| // it's flaky and it fails if the user drags it away quickly. Also note that we only want |
| // to do this for `mousedown` since doing the same for `touchstart` will stop any `click` |
| // events from firing on touch devices. |
| if (event.target && ((/** @type {?} */ (event.target))).draggable && event.type === 'mousedown') { |
| event.preventDefault(); |
| } |
| // Abort if the user is already dragging or is using a mouse button other than the primary one. |
| if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) { |
| return; |
| } |
| // If we've got handles, we need to disable the tap highlight on the entire root element, |
| // otherwise iOS will still add it, even though all the drag interactions on the handle |
| // are disabled. |
| if (this._handles.length) { |
| this._rootElementTapHighlight = rootElement.style.webkitTapHighlightColor; |
| rootElement.style.webkitTapHighlightColor = 'transparent'; |
| } |
| this._hasStartedDragging = this._hasMoved = false; |
| this._initialContainer = (/** @type {?} */ (this._dropContainer)); |
| // Avoid multiple subscriptions and memory leaks when multi touch |
| // (isDragging check above isn't enough because of possible temporal and/or dimensional delays) |
| this._removeSubscriptions(); |
| this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove); |
| this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp); |
| this._scrollSubscription = this._dragDropRegistry.scroll.pipe(startWith(null)).subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this._scrollPosition = this._viewportRuler.getViewportScrollPosition(); |
| })); |
| if (this._boundaryElement) { |
| this._boundaryRect = this._boundaryElement.getBoundingClientRect(); |
| } |
| // If we have a custom preview template, the element won't be visible anyway so we avoid the |
| // extra `getBoundingClientRect` calls and just move the preview next to the cursor. |
| this._pickupPositionInElement = this._previewTemplate && this._previewTemplate.template ? |
| { x: 0, y: 0 } : |
| this._getPointerPositionInElement(referenceElement, event); |
| /** @type {?} */ |
| const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event); |
| this._pointerDirectionDelta = { x: 0, y: 0 }; |
| this._pointerPositionAtLastDirectionChange = { x: pointerPosition.x, y: pointerPosition.y }; |
| this._dragStartTime = Date.now(); |
| this._dragDropRegistry.startDragging(this, event); |
| } |
| /** |
| * Cleans up the DOM artifacts that were added to facilitate the element being dragged. |
| * @private |
| * @param {?} event |
| * @return {?} |
| */ |
| _cleanupDragArtifacts(event) { |
| // Restore the element's visibility and insert it at its old position in the DOM. |
| // It's important that we maintain the position, because moving the element around in the DOM |
| // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary, |
| // while moving the existing elements in all other cases. |
| this._rootElement.style.display = ''; |
| if (this._nextSibling) { |
| (/** @type {?} */ (this._nextSibling.parentNode)).insertBefore(this._rootElement, this._nextSibling); |
| } |
| else { |
| coerceElement(this._initialContainer.element).appendChild(this._rootElement); |
| } |
| this._destroyPreview(); |
| this._destroyPlaceholder(); |
| this._boundaryRect = this._previewRect = undefined; |
| // Re-enter the NgZone since we bound `document` events on the outside. |
| this._ngZone.run((/** |
| * @return {?} |
| */ |
| () => { |
| /** @type {?} */ |
| const container = (/** @type {?} */ (this._dropContainer)); |
| /** @type {?} */ |
| const currentIndex = container.getItemIndex(this); |
| /** @type {?} */ |
| const pointerPosition = this._getPointerPositionOnPage(event); |
| /** @type {?} */ |
| const distance = this._getDragDistance(this._getPointerPositionOnPage(event)); |
| /** @type {?} */ |
| const isPointerOverContainer = container._isOverContainer(pointerPosition.x, pointerPosition.y); |
| this.ended.next({ source: this, distance }); |
| this.dropped.next({ |
| item: this, |
| currentIndex, |
| previousIndex: this._initialContainer.getItemIndex(this), |
| container: container, |
| previousContainer: this._initialContainer, |
| isPointerOverContainer, |
| distance |
| }); |
| container.drop(this, currentIndex, this._initialContainer, isPointerOverContainer, distance); |
| this._dropContainer = this._initialContainer; |
| })); |
| } |
| /** |
| * Updates the item's position in its drop container, or moves it |
| * into a new one, depending on its current drag position. |
| * @private |
| * @param {?} __0 |
| * @return {?} |
| */ |
| _updateActiveDropContainer({ x, y }) { |
| // Drop container that draggable has been moved into. |
| /** @type {?} */ |
| let newContainer = 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 && |
| this._initialContainer._isOverContainer(x, y)) { |
| newContainer = this._initialContainer; |
| } |
| if (newContainer && newContainer !== this._dropContainer) { |
| this._ngZone.run((/** |
| * @return {?} |
| */ |
| () => { |
| // Notify the old container that the item has left. |
| this.exited.next({ item: this, container: (/** @type {?} */ (this._dropContainer)) }); |
| (/** @type {?} */ (this._dropContainer)).exit(this); |
| // Notify the new container that the item has entered. |
| this._dropContainer = (/** @type {?} */ (newContainer)); |
| this._dropContainer.enter(this, x, y); |
| this.entered.next({ |
| item: this, |
| container: (/** @type {?} */ (newContainer)), |
| currentIndex: (/** @type {?} */ (newContainer)).getItemIndex(this) |
| }); |
| })); |
| } |
| (/** @type {?} */ (this._dropContainer))._startScrollingIfNecessary(x, y); |
| (/** @type {?} */ (this._dropContainer))._sortItem(this, x, y, this._pointerDirectionDelta); |
| this._preview.style.transform = |
| getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y); |
| } |
| /** |
| * Creates the element that will be rendered next to the user's pointer |
| * and will be used as a preview of the element that is being dragged. |
| * @private |
| * @return {?} |
| */ |
| _createPreviewElement() { |
| /** @type {?} */ |
| const previewConfig = this._previewTemplate; |
| /** @type {?} */ |
| const previewTemplate = previewConfig ? previewConfig.template : null; |
| /** @type {?} */ |
| let preview; |
| if (previewTemplate) { |
| /** @type {?} */ |
| const viewRef = (/** @type {?} */ (previewConfig)).viewContainer.createEmbeddedView(previewTemplate, (/** @type {?} */ (previewConfig)).context); |
| preview = viewRef.rootNodes[0]; |
| this._previewRef = viewRef; |
| preview.style.transform = |
| getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y); |
| } |
| else { |
| /** @type {?} */ |
| const element = this._rootElement; |
| /** @type {?} */ |
| const elementRect = element.getBoundingClientRect(); |
| preview = deepCloneNode(element); |
| preview.style.width = `${elementRect.width}px`; |
| preview.style.height = `${elementRect.height}px`; |
| preview.style.transform = getTransform(elementRect.left, elementRect.top); |
| } |
| extendStyles(preview.style, { |
| // It's important that we disable the pointer events on the preview, because |
| // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`. |
| pointerEvents: 'none', |
| position: 'fixed', |
| top: '0', |
| left: '0', |
| zIndex: '1000' |
| }); |
| toggleNativeDragInteractions(preview, false); |
| preview.classList.add('cdk-drag-preview'); |
| preview.setAttribute('dir', this._direction); |
| return preview; |
| } |
| /** |
| * Animates the preview element from its current position to the location of the drop placeholder. |
| * @private |
| * @return {?} Promise that resolves when the animation completes. |
| */ |
| _animatePreviewToPlaceholder() { |
| // If the user hasn't moved yet, the transitionend event won't fire. |
| if (!this._hasMoved) { |
| return Promise.resolve(); |
| } |
| /** @type {?} */ |
| const placeholderRect = this._placeholder.getBoundingClientRect(); |
| // Apply the class that adds a transition to the preview. |
| this._preview.classList.add('cdk-drag-animating'); |
| // Move the preview to the placeholder position. |
| this._preview.style.transform = getTransform(placeholderRect.left, placeholderRect.top); |
| // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since |
| // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to |
| // apply its style, we take advantage of the available info to figure out whether we need to |
| // bind the event in the first place. |
| /** @type {?} */ |
| const duration = getTransformTransitionDurationInMs(this._preview); |
| if (duration === 0) { |
| return Promise.resolve(); |
| } |
| return this._ngZone.runOutsideAngular((/** |
| * @return {?} |
| */ |
| () => { |
| return new Promise((/** |
| * @param {?} resolve |
| * @return {?} |
| */ |
| resolve => { |
| /** @type {?} */ |
| const handler = (/** @type {?} */ (((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| (event) => { |
| if (!event || (event.target === this._preview && event.propertyName === 'transform')) { |
| this._preview.removeEventListener('transitionend', handler); |
| resolve(); |
| clearTimeout(timeout); |
| } |
| })))); |
| // If a transition is short enough, the browser might not fire the `transitionend` event. |
| // Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll |
| // fire if the transition hasn't completed when it was supposed to. |
| /** @type {?} */ |
| const timeout = setTimeout((/** @type {?} */ (handler)), duration * 1.5); |
| this._preview.addEventListener('transitionend', handler); |
| })); |
| })); |
| } |
| /** |
| * Creates an element that will be shown instead of the current element while dragging. |
| * @private |
| * @return {?} |
| */ |
| _createPlaceholderElement() { |
| /** @type {?} */ |
| const placeholderConfig = this._placeholderTemplate; |
| /** @type {?} */ |
| const placeholderTemplate = placeholderConfig ? placeholderConfig.template : null; |
| /** @type {?} */ |
| let placeholder; |
| if (placeholderTemplate) { |
| this._placeholderRef = (/** @type {?} */ (placeholderConfig)).viewContainer.createEmbeddedView(placeholderTemplate, (/** @type {?} */ (placeholderConfig)).context); |
| placeholder = this._placeholderRef.rootNodes[0]; |
| } |
| else { |
| placeholder = deepCloneNode(this._rootElement); |
| } |
| placeholder.classList.add('cdk-drag-placeholder'); |
| return placeholder; |
| } |
| /** |
| * Figures out the coordinates at which an element was picked up. |
| * @private |
| * @param {?} referenceElement Element that initiated the dragging. |
| * @param {?} event Event that initiated the dragging. |
| * @return {?} |
| */ |
| _getPointerPositionInElement(referenceElement, event) { |
| /** @type {?} */ |
| const elementRect = this._rootElement.getBoundingClientRect(); |
| /** @type {?} */ |
| const handleElement = referenceElement === this._rootElement ? null : referenceElement; |
| /** @type {?} */ |
| const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect; |
| /** @type {?} */ |
| const point = isTouchEvent(event) ? event.targetTouches[0] : event; |
| /** @type {?} */ |
| const x = point.pageX - referenceRect.left - this._scrollPosition.left; |
| /** @type {?} */ |
| const y = point.pageY - referenceRect.top - this._scrollPosition.top; |
| return { |
| x: referenceRect.left - elementRect.left + x, |
| y: referenceRect.top - elementRect.top + y |
| }; |
| } |
| /** |
| * Determines the point of the page that was touched by the user. |
| * @private |
| * @param {?} event |
| * @return {?} |
| */ |
| _getPointerPositionOnPage(event) { |
| // `touches` will be empty for start/end events so we have to fall back to `changedTouches`. |
| /** @type {?} */ |
| const point = isTouchEvent(event) ? (event.touches[0] || event.changedTouches[0]) : event; |
| return { |
| x: point.pageX - this._scrollPosition.left, |
| y: point.pageY - this._scrollPosition.top |
| }; |
| } |
| /** |
| * Gets the pointer position on the page, accounting for any position constraints. |
| * @private |
| * @param {?} event |
| * @return {?} |
| */ |
| _getConstrainedPointerPosition(event) { |
| /** @type {?} */ |
| const point = this._getPointerPositionOnPage(event); |
| /** @type {?} */ |
| const constrainedPoint = this.constrainPosition ? this.constrainPosition(point, this) : point; |
| /** @type {?} */ |
| const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null; |
| if (this.lockAxis === 'x' || dropContainerLock === 'x') { |
| constrainedPoint.y = this._pickupPositionOnPage.y; |
| } |
| else if (this.lockAxis === 'y' || dropContainerLock === 'y') { |
| constrainedPoint.x = this._pickupPositionOnPage.x; |
| } |
| if (this._boundaryRect) { |
| const { x: pickupX, y: pickupY } = this._pickupPositionInElement; |
| /** @type {?} */ |
| const boundaryRect = this._boundaryRect; |
| /** @type {?} */ |
| const previewRect = (/** @type {?} */ (this._previewRect)); |
| /** @type {?} */ |
| const minY = boundaryRect.top + pickupY; |
| /** @type {?} */ |
| const maxY = boundaryRect.bottom - (previewRect.height - pickupY); |
| /** @type {?} */ |
| const minX = boundaryRect.left + pickupX; |
| /** @type {?} */ |
| const maxX = boundaryRect.right - (previewRect.width - pickupX); |
| constrainedPoint.x = clamp(constrainedPoint.x, minX, maxX); |
| constrainedPoint.y = clamp(constrainedPoint.y, minY, maxY); |
| } |
| return constrainedPoint; |
| } |
| /** |
| * Updates the current drag delta, based on the user's current pointer position on the page. |
| * @private |
| * @param {?} pointerPositionOnPage |
| * @return {?} |
| */ |
| _updatePointerDirectionDelta(pointerPositionOnPage) { |
| const { x, y } = pointerPositionOnPage; |
| /** @type {?} */ |
| const delta = this._pointerDirectionDelta; |
| /** @type {?} */ |
| const positionSinceLastChange = this._pointerPositionAtLastDirectionChange; |
| // Amount of pixels the user has dragged since the last time the direction changed. |
| /** @type {?} */ |
| const changeX = Math.abs(x - positionSinceLastChange.x); |
| /** @type {?} */ |
| const changeY = Math.abs(y - positionSinceLastChange.y); |
| // Because we handle pointer events on a per-pixel basis, we don't want the delta |
| // to change for every pixel, otherwise anything that depends on it can look erratic. |
| // To make the delta more consistent, we track how much the user has moved since the last |
| // delta change and we only update it after it has reached a certain threshold. |
| if (changeX > this._config.pointerDirectionChangeThreshold) { |
| delta.x = x > positionSinceLastChange.x ? 1 : -1; |
| positionSinceLastChange.x = x; |
| } |
| if (changeY > this._config.pointerDirectionChangeThreshold) { |
| delta.y = y > positionSinceLastChange.y ? 1 : -1; |
| positionSinceLastChange.y = y; |
| } |
| return delta; |
| } |
| /** |
| * Toggles the native drag interactions, based on how many handles are registered. |
| * @private |
| * @return {?} |
| */ |
| _toggleNativeDragInteractions() { |
| if (!this._rootElement || !this._handles) { |
| return; |
| } |
| /** @type {?} */ |
| const shouldEnable = this._handles.length > 0 || !this.isDragging(); |
| if (shouldEnable !== this._nativeInteractionsEnabled) { |
| this._nativeInteractionsEnabled = shouldEnable; |
| toggleNativeDragInteractions(this._rootElement, shouldEnable); |
| } |
| } |
| /** |
| * Removes the manually-added event listeners from the root element. |
| * @private |
| * @param {?} element |
| * @return {?} |
| */ |
| _removeRootElementListeners(element) { |
| element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions); |
| element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions); |
| } |
| /** |
| * Applies a `transform` to the root element, taking into account any existing transforms on it. |
| * @private |
| * @param {?} x New transform value along the X axis. |
| * @param {?} y New transform value along the Y axis. |
| * @return {?} |
| */ |
| _applyRootElementTransform(x, y) { |
| /** @type {?} */ |
| const transform = getTransform(x, y); |
| // Cache the previous transform amount only after the first drag sequence, because |
| // we don't want our own transforms to stack on top of each other. |
| if (this._initialTransform == null) { |
| this._initialTransform = this._rootElement.style.transform || ''; |
| } |
| // Preserve the previous `transform` value, if there was one. Note that we apply our own |
| // transform before the user's, because things like rotation can affect which direction |
| // the element will be translated towards. |
| this._rootElement.style.transform = this._initialTransform ? |
| transform + ' ' + this._initialTransform : transform; |
| } |
| /** |
| * Gets the distance that the user has dragged during the current drag sequence. |
| * @private |
| * @param {?} currentPosition Current position of the user's pointer. |
| * @return {?} |
| */ |
| _getDragDistance(currentPosition) { |
| /** @type {?} */ |
| const pickupPosition = this._pickupPositionOnPage; |
| if (pickupPosition) { |
| return { x: currentPosition.x - pickupPosition.x, y: currentPosition.y - pickupPosition.y }; |
| } |
| return { x: 0, y: 0 }; |
| } |
| } |
| /** |
| * Gets a 3d `transform` that can be applied to an element. |
| * @param {?} x Desired position of the element along the X axis. |
| * @param {?} y Desired position of the element along the Y axis. |
| * @return {?} |
| */ |
| function getTransform(x, y) { |
| // Round the transforms since some browsers will |
| // blur the elements for sub-pixel transforms. |
| return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`; |
| } |
| /** |
| * Creates a deep clone of an element. |
| * @param {?} node |
| * @return {?} |
| */ |
| function deepCloneNode(node) { |
| /** @type {?} */ |
| const clone = (/** @type {?} */ (node.cloneNode(true))); |
| /** @type {?} */ |
| const descendantsWithId = clone.querySelectorAll('[id]'); |
| /** @type {?} */ |
| const descendantCanvases = node.querySelectorAll('canvas'); |
| // Remove the `id` to avoid having multiple elements with the same id on the page. |
| clone.removeAttribute('id'); |
| for (let i = 0; i < descendantsWithId.length; i++) { |
| descendantsWithId[i].removeAttribute('id'); |
| } |
| // `cloneNode` won't transfer the content of `canvas` elements so we have to do it ourselves. |
| // We match up the cloned canvas to their sources using their index in the DOM. |
| if (descendantCanvases.length) { |
| /** @type {?} */ |
| const cloneCanvases = clone.querySelectorAll('canvas'); |
| for (let i = 0; i < descendantCanvases.length; i++) { |
| /** @type {?} */ |
| const correspondingCloneContext = cloneCanvases[i].getContext('2d'); |
| if (correspondingCloneContext) { |
| correspondingCloneContext.drawImage(descendantCanvases[i], 0, 0); |
| } |
| } |
| } |
| return clone; |
| } |
| /** |
| * Clamps a value between a minimum and a maximum. |
| * @param {?} value |
| * @param {?} min |
| * @param {?} max |
| * @return {?} |
| */ |
| function clamp(value, min, max) { |
| return Math.max(min, Math.min(max, value)); |
| } |
| /** |
| * Helper to remove an element from the DOM and to do all the necessary null checks. |
| * @param {?} element Element to be removed. |
| * @return {?} |
| */ |
| function removeElement(element) { |
| if (element && element.parentNode) { |
| element.parentNode.removeChild(element); |
| } |
| } |
| /** |
| * Determines whether an event is a touch event. |
| * @param {?} event |
| * @return {?} |
| */ |
| function isTouchEvent(event) { |
| // This function is called for every pixel that the user has dragged so we need it to be |
| // as fast as possible. Since we only bind mouse events and touch events, we can assume |
| // that if the event's name starts with `t`, it's a touch event. |
| return event.type[0] === 't'; |
| } |
| /** |
| * Gets the element into which the drag preview should be inserted. |
| * @param {?} documentRef |
| * @return {?} |
| */ |
| function getPreviewInsertionPoint(documentRef) { |
| // We can't use the body if the user is in fullscreen mode, |
| // because the preview will render under the fullscreen element. |
| // TODO(crisbeto): dedupe this with the `FullscreenOverlayContainer` eventually. |
| return documentRef.fullscreenElement || |
| documentRef.webkitFullscreenElement || |
| documentRef.mozFullScreenElement || |
| documentRef.msFullscreenElement || |
| documentRef.body; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * Moves an item one index in an array to another. |
| * @template T |
| * @param {?} array Array in which to move the item. |
| * @param {?} fromIndex Starting index of the item. |
| * @param {?} toIndex Index to which the item should be moved. |
| * @return {?} |
| */ |
| function moveItemInArray(array, fromIndex, toIndex) { |
| /** @type {?} */ |
| const from = clamp$1(fromIndex, array.length - 1); |
| /** @type {?} */ |
| const to = clamp$1(toIndex, array.length - 1); |
| if (from === to) { |
| return; |
| } |
| /** @type {?} */ |
| const target = array[from]; |
| /** @type {?} */ |
| const delta = to < from ? -1 : 1; |
| for (let i = from; i !== to; i += delta) { |
| array[i] = array[i + delta]; |
| } |
| array[to] = target; |
| } |
| /** |
| * Moves an item from one array to another. |
| * @template T |
| * @param {?} currentArray Array from which to transfer the item. |
| * @param {?} targetArray Array into which to put the item. |
| * @param {?} currentIndex Index of the item in its current array. |
| * @param {?} targetIndex Index at which to insert the item. |
| * @return {?} |
| */ |
| function transferArrayItem(currentArray, targetArray, currentIndex, targetIndex) { |
| /** @type {?} */ |
| const from = clamp$1(currentIndex, currentArray.length - 1); |
| /** @type {?} */ |
| const to = clamp$1(targetIndex, targetArray.length); |
| if (currentArray.length) { |
| targetArray.splice(to, 0, currentArray.splice(from, 1)[0]); |
| } |
| } |
| /** |
| * Copies an item from one array to another, leaving it in its |
| * original position in current array. |
| * @template T |
| * @param {?} currentArray Array from which to copy the item. |
| * @param {?} targetArray Array into which is copy the item. |
| * @param {?} currentIndex Index of the item in its current array. |
| * @param {?} targetIndex Index at which to insert the item. |
| * |
| * @return {?} |
| */ |
| function copyArrayItem(currentArray, targetArray, currentIndex, targetIndex) { |
| /** @type {?} */ |
| const to = clamp$1(targetIndex, targetArray.length); |
| if (currentArray.length) { |
| targetArray.splice(to, 0, currentArray[currentIndex]); |
| } |
| } |
| /** |
| * Clamps a number between zero and a maximum. |
| * @param {?} value |
| * @param {?} max |
| * @return {?} |
| */ |
| function clamp$1(value, max) { |
| return Math.max(0, Math.min(max, value)); |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Counter used to generate unique ids for drop refs. |
| * @type {?} |
| */ |
| let _uniqueIdCounter = 0; |
| /** |
| * 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; |
| /** |
| * Reference to a drop list. Used to manipulate or dispose of the container. |
| * \@docs-private |
| * @template T |
| */ |
| 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; |
| /** |
| * Unique ID for the drop list. |
| * @deprecated No longer being used. To be removed. |
| * \@breaking-change 8.0.0 |
| */ |
| this.id = `cdk-drop-list-ref-${_uniqueIdCounter++}`; |
| /** |
| * Whether starting a dragging sequence from this container is disabled. |
| */ |
| this.disabled = false; |
| /** |
| * Whether sorting items within the list is disabled. |
| */ |
| this.sortingDisabled = true; |
| /** |
| * 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(); |
| /** |
| * 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); |
| } |
| })); |
| }); |
| _dragDropRegistry.registerDropContainer(this); |
| this._document = _document; |
| this.element = element instanceof ElementRef ? element.nativeElement : element; |
| } |
| /** |
| * 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(); |
| // @breaking-change 9.0.0 Remove check for _ngZone once it's marked as a required param. |
| if (this._ngZone) { |
| this._ngZone.runOutsideAngular((/** |
| * @return {?} |
| */ |
| () => element.addEventListener('scroll', this._handleScroll))); |
| } |
| else { |
| element.addEventListener('scroll', this._handleScroll); |
| } |
| // @breaking-change 9.0.0 Remove check for _viewportRuler once it's marked as a required param. |
| if (this._viewportRuler) { |
| this._viewportScrollPosition = 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); |
| } |
| })); |
| } |
| } |
| /** |
| * 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. |
| * \@breaking-change 9.0.0 `distance` parameter to become required. |
| * @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 = { x: 0, y: 0 }) { |
| 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 */; |
| // @breaking-change 9.0.0 Remove null check for _viewportRuler once it's a required parameter. |
| // Check whether we're in range to scroll the viewport. |
| if (this._viewportRuler) { |
| 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 we couldn't find a scroll direction based on the |
| // window, try with the container, if the pointer is close by. |
| if (!verticalScrollDirection && !horizontalScrollDirection && |
| this._isPointerNearDropContainer(pointerX, pointerY)) { |
| verticalScrollDirection = getVerticalScrollDirection(this._clientRect, pointerY); |
| horizontalScrollDirection = getHorizontalScrollDirection(this._clientRect, pointerX); |
| scrollNode = coerceElement(this.element); |
| } |
| // TODO(crisbeto): we also need to account for whether the view or element are scrollable in |
| // the first place. With the current approach we'll still try to scroll them, but it just |
| // won't do anything. The only case where this is relevant is that if we have a scrollable |
| // list close to the viewport edge where the viewport isn't scrollable. In this case the |
| // we'll be trying to scroll the viewport rather than the list. |
| if (scrollNode && (verticalScrollDirection !== this._verticalScrollDirection || |
| horizontalScrollDirection !== this._horizontalScrollDirection || |
| scrollNode !== this._scrollNode)) { |
| this._verticalScrollDirection = verticalScrollDirection; |
| this._horizontalScrollDirection = horizontalScrollDirection; |
| this._scrollNode = scrollNode; |
| if ((verticalScrollDirection || horizontalScrollDirection) && scrollNode) { |
| // @breaking-change 9.0.0 Remove null check for `_ngZone` once it is made required. |
| if (this._ngZone) { |
| this._ngZone.runOutsideAngular(this._startScrollInterval); |
| } |
| else { |
| 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 (!this.enterPredicate(item, this) || !isInsideClientRect(this._clientRect, x, y)) { |
| return false; |
| } |
| /** @type {?} */ |
| const elementFromPoint = (/** @type {?} */ (this._document.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(); |
| } |
| } |
| /** |
| * Called by a connected drop list when dragging has stopped. |
| * @param {?} sibling Sibling whose dragging has stopped. |
| * @return {?} |
| */ |
| _stopReceiving(sibling) { |
| this._activeSiblings.delete(sibling); |
| } |
| } |
| /** |
| * 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 */; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Event options that can be used to bind an active, capturing event. |
| * @type {?} |
| */ |
| const activeCapturingEventOptions = normalizePassiveListenerOptions({ |
| passive: false, |
| capture: true |
| }); |
| /** |
| * Service that keeps track of all the drag item and drop container |
| * instances, and manages global event listeners on the `document`. |
| * \@docs-private |
| * @template I, C |
| */ |
| // Note: this class is generic, rather than referencing CdkDrag and CdkDropList directly, in order |
| // to avoid circular imports. If we were to reference them here, importing the registry into the |
| // classes that are registering themselves will introduce a circular import. |
| class DragDropRegistry { |
| /** |
| * @param {?} _ngZone |
| * @param {?} _document |
| */ |
| constructor(_ngZone, _document) { |
| this._ngZone = _ngZone; |
| /** |
| * Registered drop container instances. |
| */ |
| this._dropInstances = new Set(); |
| /** |
| * Registered drag item instances. |
| */ |
| this._dragInstances = new Set(); |
| /** |
| * Drag item instances that are currently being dragged. |
| */ |
| this._activeDragInstances = new Set(); |
| /** |
| * Keeps track of the event listeners that we've bound to the `document`. |
| */ |
| this._globalListeners = new Map(); |
| /** |
| * Emits the `touchmove` or `mousemove` events that are dispatched |
| * while the user is dragging a drag item instance. |
| */ |
| this.pointerMove = new Subject(); |
| /** |
| * Emits the `touchend` or `mouseup` events that are dispatched |
| * while the user is dragging a drag item instance. |
| */ |
| this.pointerUp = new Subject(); |
| /** |
| * Emits when the viewport has been scrolled while the user is dragging an item. |
| */ |
| this.scroll = new Subject(); |
| /** |
| * Event listener that will prevent the default browser action while the user is dragging. |
| * @param event Event whose default action should be prevented. |
| */ |
| this._preventDefaultWhileDragging = (/** |
| * @param {?} event |
| * @return {?} |
| */ |
| (event) => { |
| if (this._activeDragInstances.size) { |
| event.preventDefault(); |
| } |
| }); |
| this._document = _document; |
| } |
| /** |
| * Adds a drop container to the registry. |
| * @param {?} drop |
| * @return {?} |
| */ |
| registerDropContainer(drop) { |
| if (!this._dropInstances.has(drop)) { |
| if (this.getDropContainer(drop.id)) { |
| throw Error(`Drop instance with id "${drop.id}" has already been registered.`); |
| } |
| this._dropInstances.add(drop); |
| } |
| } |
| /** |
| * Adds a drag item instance to the registry. |
| * @param {?} drag |
| * @return {?} |
| */ |
| registerDragItem(drag) { |
| this._dragInstances.add(drag); |
| // The `touchmove` event gets bound once, ahead of time, because WebKit |
| // won't preventDefault on a dynamically-added `touchmove` listener. |
| // See https://bugs.webkit.org/show_bug.cgi?id=184250. |
| if (this._dragInstances.size === 1) { |
| this._ngZone.runOutsideAngular((/** |
| * @return {?} |
| */ |
| () => { |
| // The event handler has to be explicitly active, |
| // because newer browsers make it passive by default. |
| this._document.addEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions); |
| })); |
| } |
| } |
| /** |
| * Removes a drop container from the registry. |
| * @param {?} drop |
| * @return {?} |
| */ |
| removeDropContainer(drop) { |
| this._dropInstances.delete(drop); |
| } |
| /** |
| * Removes a drag item instance from the registry. |
| * @param {?} drag |
| * @return {?} |
| */ |
| removeDragItem(drag) { |
| this._dragInstances.delete(drag); |
| this.stopDragging(drag); |
| if (this._dragInstances.size === 0) { |
| this._document.removeEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions); |
| } |
| } |
| /** |
| * Starts the dragging sequence for a drag instance. |
| * @param {?} drag Drag instance which is being dragged. |
| * @param {?} event Event that initiated the dragging. |
| * @return {?} |
| */ |
| startDragging(drag, event) { |
| // Do not process the same drag twice to avoid memory leaks and redundant listeners |
| if (this._activeDragInstances.has(drag)) { |
| return; |
| } |
| this._activeDragInstances.add(drag); |
| if (this._activeDragInstances.size === 1) { |
| /** @type {?} */ |
| const isTouchEvent = event.type.startsWith('touch'); |
| /** @type {?} */ |
| const moveEvent = isTouchEvent ? 'touchmove' : 'mousemove'; |
| /** @type {?} */ |
| const upEvent = isTouchEvent ? 'touchend' : 'mouseup'; |
| // We explicitly bind __active__ listeners here, because newer browsers will default to |
| // passive ones for `mousemove` and `touchmove`. The events need to be active, because we |
| // use `preventDefault` to prevent the page from scrolling while the user is dragging. |
| this._globalListeners |
| .set(moveEvent, { |
| handler: (/** |
| * @param {?} e |
| * @return {?} |
| */ |
| (e) => this.pointerMove.next((/** @type {?} */ (e)))), |
| options: activeCapturingEventOptions |
| }) |
| .set(upEvent, { |
| handler: (/** |
| * @param {?} e |
| * @return {?} |
| */ |
| (e) => this.pointerUp.next((/** @type {?} */ (e)))), |
| options: true |
| }) |
| .set('scroll', { |
| handler: (/** |
| * @param {?} e |
| * @return {?} |
| */ |
| (e) => this.scroll.next(e)) |
| }) |
| // Preventing the default action on `mousemove` isn't enough to disable text selection |
| // on Safari so we need to prevent the selection event as well. Alternatively this can |
| // be done by setting `user-select: none` on the `body`, however it has causes a style |
| // recalculation which can be expensive on pages with a lot of elements. |
| .set('selectstart', { |
| handler: this._preventDefaultWhileDragging, |
| options: activeCapturingEventOptions |
| }); |
| this._ngZone.runOutsideAngular((/** |
| * @return {?} |
| */ |
| () => { |
| this._globalListeners.forEach((/** |
| * @param {?} config |
| * @param {?} name |
| * @return {?} |
| */ |
| (config, name) => { |
| this._document.addEventListener(name, config.handler, config.options); |
| })); |
| })); |
| } |
| } |
| /** |
| * Stops dragging a drag item instance. |
| * @param {?} drag |
| * @return {?} |
| */ |
| stopDragging(drag) { |
| this._activeDragInstances.delete(drag); |
| if (this._activeDragInstances.size === 0) { |
| this._clearGlobalListeners(); |
| } |
| } |
| /** |
| * Gets whether a drag item instance is currently being dragged. |
| * @param {?} drag |
| * @return {?} |
| */ |
| isDragging(drag) { |
| return this._activeDragInstances.has(drag); |
| } |
| /** |
| * Gets a drop container by its id. |
| * @deprecated No longer being used. To be removed. |
| * \@breaking-change 8.0.0 |
| * @param {?} id |
| * @return {?} |
| */ |
| getDropContainer(id) { |
| return Array.from(this._dropInstances).find((/** |
| * @param {?} instance |
| * @return {?} |
| */ |
| instance => instance.id === id)); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| this._dragInstances.forEach((/** |
| * @param {?} instance |
| * @return {?} |
| */ |
| instance => this.removeDragItem(instance))); |
| this._dropInstances.forEach((/** |
| * @param {?} instance |
| * @return {?} |
| */ |
| instance => this.removeDropContainer(instance))); |
| this._clearGlobalListeners(); |
| this.pointerMove.complete(); |
| this.pointerUp.complete(); |
| } |
| /** |
| * Clears out the global event listeners from the `document`. |
| * @private |
| * @return {?} |
| */ |
| _clearGlobalListeners() { |
| this._globalListeners.forEach((/** |
| * @param {?} config |
| * @param {?} name |
| * @return {?} |
| */ |
| (config, name) => { |
| this._document.removeEventListener(name, config.handler, config.options); |
| })); |
| this._globalListeners.clear(); |
| } |
| } |
| DragDropRegistry.decorators = [ |
| { type: Injectable, args: [{ providedIn: 'root' },] }, |
| ]; |
| /** @nocollapse */ |
| DragDropRegistry.ctorParameters = () => [ |
| { type: NgZone }, |
| { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } |
| ]; |
| /** @nocollapse */ DragDropRegistry.ngInjectableDef = ɵɵdefineInjectable({ factory: function DragDropRegistry_Factory() { return new DragDropRegistry(ɵɵinject(NgZone), ɵɵinject(DOCUMENT)); }, token: DragDropRegistry, providedIn: "root" }); |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Default configuration to be used when creating a `DragRef`. |
| * @type {?} |
| */ |
| const DEFAULT_CONFIG = { |
| dragStartThreshold: 5, |
| pointerDirectionChangeThreshold: 5 |
| }; |
| /** |
| * Service that allows for drag-and-drop functionality to be attached to DOM elements. |
| */ |
| class DragDrop { |
| /** |
| * @param {?} _document |
| * @param {?} _ngZone |
| * @param {?} _viewportRuler |
| * @param {?} _dragDropRegistry |
| */ |
| constructor(_document, _ngZone, _viewportRuler, _dragDropRegistry) { |
| this._document = _document; |
| this._ngZone = _ngZone; |
| this._viewportRuler = _viewportRuler; |
| this._dragDropRegistry = _dragDropRegistry; |
| } |
| /** |
| * Turns an element into a draggable item. |
| * @template T |
| * @param {?} element Element to which to attach the dragging functionality. |
| * @param {?=} config Object used to configure the dragging behavior. |
| * @return {?} |
| */ |
| createDrag(element, config = DEFAULT_CONFIG) { |
| return new DragRef(element, config, this._document, this._ngZone, this._viewportRuler, this._dragDropRegistry); |
| } |
| /** |
| * Turns an element into a drop list. |
| * @template T |
| * @param {?} element Element to which to attach the drop list functionality. |
| * @return {?} |
| */ |
| createDropList(element) { |
| return new DropListRef(element, this._dragDropRegistry, this._document, this._ngZone, this._viewportRuler); |
| } |
| } |
| DragDrop.decorators = [ |
| { type: Injectable, args: [{ providedIn: 'root' },] }, |
| ]; |
| /** @nocollapse */ |
| DragDrop.ctorParameters = () => [ |
| { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, |
| { type: NgZone }, |
| { type: ViewportRuler }, |
| { type: DragDropRegistry } |
| ]; |
| /** @nocollapse */ DragDrop.ngInjectableDef = ɵɵdefineInjectable({ factory: function DragDrop_Factory() { return new DragDrop(ɵɵinject(DOCUMENT), ɵɵinject(NgZone), ɵɵinject(ViewportRuler), ɵɵinject(DragDropRegistry)); }, token: DragDrop, providedIn: "root" }); |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Injection token that is used to provide a CdkDropList instance to CdkDrag. |
| * Used for avoiding circular imports. |
| * @type {?} |
| */ |
| const CDK_DROP_LIST = new InjectionToken('CDK_DROP_LIST'); |
| /** |
| * Injection token that is used to provide a CdkDropList instance to CdkDrag. |
| * Used for avoiding circular imports. |
| * @deprecated Use `CDK_DROP_LIST` instead. |
| * \@breaking-change 8.0.0 |
| * @type {?} |
| */ |
| const CDK_DROP_LIST_CONTAINER = CDK_DROP_LIST; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Injection token that can be used for a `CdkDrag` to provide itself as a parent to the |
| * drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily |
| * to avoid circular imports. |
| * \@docs-private |
| * @type {?} |
| */ |
| const CDK_DRAG_PARENT = new InjectionToken('CDK_DRAG_PARENT'); |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Handle that can be used to drag and CdkDrag instance. |
| */ |
| class CdkDragHandle { |
| /** |
| * @param {?} element |
| * @param {?=} parentDrag |
| */ |
| constructor(element, parentDrag) { |
| this.element = element; |
| /** |
| * Emits when the state of the handle has changed. |
| */ |
| this._stateChanges = new Subject(); |
| this._disabled = false; |
| this._parentDrag = parentDrag; |
| toggleNativeDragInteractions(element.nativeElement, false); |
| } |
| /** |
| * Whether starting to drag through this handle is disabled. |
| * @return {?} |
| */ |
| get disabled() { return this._disabled; } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| this._stateChanges.next(this); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| this._stateChanges.complete(); |
| } |
| } |
| CdkDragHandle.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[cdkDragHandle]', |
| host: { |
| 'class': 'cdk-drag-handle' |
| } |
| },] }, |
| ]; |
| /** @nocollapse */ |
| CdkDragHandle.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_PARENT,] }, { type: Optional }] } |
| ]; |
| CdkDragHandle.propDecorators = { |
| disabled: [{ type: Input, args: ['cdkDragHandleDisabled',] }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Element that will be used as a template for the placeholder of a CdkDrag when |
| * it is being dragged. The placeholder is displayed in place of the element being dragged. |
| * @template T |
| */ |
| class CdkDragPlaceholder { |
| /** |
| * @param {?} templateRef |
| */ |
| constructor(templateRef) { |
| this.templateRef = templateRef; |
| } |
| } |
| CdkDragPlaceholder.decorators = [ |
| { type: Directive, args: [{ |
| selector: 'ng-template[cdkDragPlaceholder]' |
| },] }, |
| ]; |
| /** @nocollapse */ |
| CdkDragPlaceholder.ctorParameters = () => [ |
| { type: TemplateRef } |
| ]; |
| CdkDragPlaceholder.propDecorators = { |
| data: [{ type: Input }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Element that will be used as a template for the preview |
| * of a CdkDrag when it is being dragged. |
| * @template T |
| */ |
| class CdkDragPreview { |
| /** |
| * @param {?} templateRef |
| */ |
| constructor(templateRef) { |
| this.templateRef = templateRef; |
| } |
| } |
| CdkDragPreview.decorators = [ |
| { type: Directive, args: [{ |
| selector: 'ng-template[cdkDragPreview]' |
| },] }, |
| ]; |
| /** @nocollapse */ |
| CdkDragPreview.ctorParameters = () => [ |
| { type: TemplateRef } |
| ]; |
| CdkDragPreview.propDecorators = { |
| data: [{ type: Input }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Injection token that can be used to configure the behavior of `CdkDrag`. |
| * @type {?} |
| */ |
| const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG', { |
| providedIn: 'root', |
| factory: CDK_DRAG_CONFIG_FACTORY |
| }); |
| /** |
| * \@docs-private |
| * @return {?} |
| */ |
| function CDK_DRAG_CONFIG_FACTORY() { |
| return { dragStartThreshold: 5, pointerDirectionChangeThreshold: 5 }; |
| } |
| /** |
| * Element that can be moved inside a CdkDropList container. |
| * @template T |
| */ |
| class CdkDrag { |
| /** |
| * @param {?} element |
| * @param {?} dropContainer |
| * @param {?} _document |
| * @param {?} _ngZone |
| * @param {?} _viewContainerRef |
| * @param {?} config |
| * @param {?} _dir |
| * @param {?} dragDrop |
| * @param {?} _changeDetectorRef |
| */ |
| constructor(element, dropContainer, _document, _ngZone, _viewContainerRef, config, _dir, dragDrop, _changeDetectorRef) { |
| this.element = element; |
| this.dropContainer = dropContainer; |
| this._document = _document; |
| this._ngZone = _ngZone; |
| this._viewContainerRef = _viewContainerRef; |
| this._dir = _dir; |
| this._changeDetectorRef = _changeDetectorRef; |
| this._destroyed = new Subject(); |
| /** |
| * Amount of milliseconds to wait after the user has put their |
| * pointer down before starting to drag the element. |
| */ |
| this.dragStartDelay = 0; |
| this._disabled = false; |
| /** |
| * Emits when the user starts dragging the item. |
| */ |
| this.started = new EventEmitter(); |
| /** |
| * Emits when the user has released a drag item, before any animations have started. |
| */ |
| this.released = new EventEmitter(); |
| /** |
| * Emits when the user stops dragging an item in the container. |
| */ |
| this.ended = new EventEmitter(); |
| /** |
| * Emits when the user has moved the item into a new container. |
| */ |
| this.entered = new EventEmitter(); |
| /** |
| * Emits when the user removes the item its container by dragging it into another container. |
| */ |
| this.exited = new EventEmitter(); |
| /** |
| * Emits when the user drops the item inside a container. |
| */ |
| this.dropped = new EventEmitter(); |
| /** |
| * Emits as the user is dragging the item. Use with caution, |
| * because this event will fire for every pixel that the user has dragged. |
| */ |
| this.moved = new Observable((/** |
| * @param {?} observer |
| * @return {?} |
| */ |
| (observer) => { |
| /** @type {?} */ |
| const subscription = this._dragRef.moved.pipe(map((/** |
| * @param {?} movedEvent |
| * @return {?} |
| */ |
| movedEvent => ({ |
| source: this, |
| pointerPosition: movedEvent.pointerPosition, |
| event: movedEvent.event, |
| delta: movedEvent.delta, |
| distance: movedEvent.distance |
| })))).subscribe(observer); |
| return (/** |
| * @return {?} |
| */ |
| () => { |
| subscription.unsubscribe(); |
| }); |
| })); |
| this._dragRef = dragDrop.createDrag(element, config); |
| this._dragRef.data = this; |
| this._syncInputs(this._dragRef); |
| this._handleEvents(this._dragRef); |
| } |
| /** |
| * Selector that will be used to determine the element to which the draggable's position will |
| * be constrained. Matching starts from the element's parent and goes up the DOM until a matching |
| * element has been found |
| * @deprecated Use `boundaryElement` instead. |
| * \@breaking-change 9.0.0 |
| * @return {?} |
| */ |
| get boundaryElementSelector() { |
| return typeof this.boundaryElement === 'string' ? this.boundaryElement : (/** @type {?} */ (undefined)); |
| } |
| /** |
| * @param {?} selector |
| * @return {?} |
| */ |
| set boundaryElementSelector(selector) { |
| this.boundaryElement = selector; |
| } |
| /** |
| * Whether starting to drag this element is disabled. |
| * @return {?} |
| */ |
| get disabled() { |
| return this._disabled || (this.dropContainer && this.dropContainer.disabled); |
| } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| this._dragRef.disabled = this._disabled; |
| } |
| /** |
| * Returns the element that is being used as a placeholder |
| * while the current element is being dragged. |
| * @return {?} |
| */ |
| getPlaceholderElement() { |
| return this._dragRef.getPlaceholderElement(); |
| } |
| /** |
| * Returns the root draggable element. |
| * @return {?} |
| */ |
| getRootElement() { |
| return this._dragRef.getRootElement(); |
| } |
| /** |
| * Resets a standalone drag item to its initial position. |
| * @return {?} |
| */ |
| reset() { |
| this._dragRef.reset(); |
| } |
| /** |
| * Gets the pixel coordinates of the draggable outside of a drop container. |
| * @return {?} |
| */ |
| getFreeDragPosition() { |
| return this._dragRef.getFreeDragPosition(); |
| } |
| /** |
| * @return {?} |
| */ |
| ngAfterViewInit() { |
| // We need to wait for the zone to stabilize, in order for the reference |
| // element to be in the proper place in the DOM. This is mostly relevant |
| // for draggable elements inside portals since they get stamped out in |
| // their original DOM position and then they get transferred to the portal. |
| this._ngZone.onStable.asObservable() |
| .pipe(take(1), takeUntil(this._destroyed)) |
| .subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this._updateRootElement(); |
| // Listen for any newly-added handles. |
| this._handles.changes.pipe(startWith(this._handles), |
| // Sync the new handles with the DragRef. |
| tap((/** |
| * @param {?} handles |
| * @return {?} |
| */ |
| (handles) => { |
| /** @type {?} */ |
| const childHandleElements = handles |
| .filter((/** |
| * @param {?} handle |
| * @return {?} |
| */ |
| handle => handle._parentDrag === this)) |
| .map((/** |
| * @param {?} handle |
| * @return {?} |
| */ |
| handle => handle.element)); |
| this._dragRef.withHandles(childHandleElements); |
| })), |
| // Listen if the state of any of the handles changes. |
| switchMap((/** |
| * @param {?} handles |
| * @return {?} |
| */ |
| (handles) => { |
| return merge(...handles.map((/** |
| * @param {?} item |
| * @return {?} |
| */ |
| item => item._stateChanges))); |
| })), takeUntil(this._destroyed)).subscribe((/** |
| * @param {?} handleInstance |
| * @return {?} |
| */ |
| handleInstance => { |
| // Enabled/disable the handle that changed in the DragRef. |
| /** @type {?} */ |
| const dragRef = this._dragRef; |
| /** @type {?} */ |
| const handle = handleInstance.element.nativeElement; |
| handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle); |
| })); |
| if (this.freeDragPosition) { |
| this._dragRef.setFreeDragPosition(this.freeDragPosition); |
| } |
| })); |
| } |
| /** |
| * @param {?} changes |
| * @return {?} |
| */ |
| ngOnChanges(changes) { |
| /** @type {?} */ |
| const rootSelectorChange = changes['rootElementSelector']; |
| /** @type {?} */ |
| const positionChange = changes['freeDragPosition']; |
| // We don't have to react to the first change since it's being |
| // handled in `ngAfterViewInit` where it needs to be deferred. |
| if (rootSelectorChange && !rootSelectorChange.firstChange) { |
| this._updateRootElement(); |
| } |
| // Skip the first change since it's being handled in `ngAfterViewInit`. |
| if (positionChange && !positionChange.firstChange && this.freeDragPosition) { |
| this._dragRef.setFreeDragPosition(this.freeDragPosition); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| this._destroyed.next(); |
| this._destroyed.complete(); |
| this._dragRef.dispose(); |
| } |
| /** |
| * Syncs the root element with the `DragRef`. |
| * @private |
| * @return {?} |
| */ |
| _updateRootElement() { |
| /** @type {?} */ |
| const element = this.element.nativeElement; |
| /** @type {?} */ |
| const rootElement = this.rootElementSelector ? |
| getClosestMatchingAncestor(element, this.rootElementSelector) : element; |
| if (rootElement && rootElement.nodeType !== this._document.ELEMENT_NODE) { |
| throw Error(`cdkDrag must be attached to an element node. ` + |
| `Currently attached to "${rootElement.nodeName}".`); |
| } |
| this._dragRef.withRootElement(rootElement || element); |
| } |
| /** |
| * Gets the boundary element, based on the `boundaryElement` value. |
| * @private |
| * @return {?} |
| */ |
| _getBoundaryElement() { |
| /** @type {?} */ |
| const boundary = this.boundaryElement; |
| if (!boundary) { |
| return null; |
| } |
| if (typeof boundary === 'string') { |
| return getClosestMatchingAncestor(this.element.nativeElement, boundary); |
| } |
| /** @type {?} */ |
| const element = coerceElement(boundary); |
| if (isDevMode() && !element.contains(this.element.nativeElement)) { |
| throw Error('Draggable element is not inside of the node passed into cdkDragBoundary.'); |
| } |
| return element; |
| } |
| /** |
| * Syncs the inputs of the CdkDrag with the options of the underlying DragRef. |
| * @private |
| * @param {?} ref |
| * @return {?} |
| */ |
| _syncInputs(ref) { |
| ref.beforeStarted.subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| if (!ref.isDragging()) { |
| /** @type {?} */ |
| const dir = this._dir; |
| /** @type {?} */ |
| const placeholder = this._placeholderTemplate ? { |
| template: this._placeholderTemplate.templateRef, |
| context: this._placeholderTemplate.data, |
| viewContainer: this._viewContainerRef |
| } : null; |
| /** @type {?} */ |
| 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.dragStartDelay = coerceNumberProperty(this.dragStartDelay); |
| ref.constrainPosition = this.constrainPosition; |
| ref |
| .withBoundaryElement(this._getBoundaryElement()) |
| .withPlaceholderTemplate(placeholder) |
| .withPreviewTemplate(preview); |
| if (dir) { |
| ref.withDirection(dir.value); |
| } |
| } |
| })); |
| } |
| /** |
| * Handles the events from the underlying `DragRef`. |
| * @private |
| * @param {?} ref |
| * @return {?} |
| */ |
| _handleEvents(ref) { |
| ref.started.subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this.started.emit({ source: this }); |
| // Since all of these events run outside of change detection, |
| // we need to ensure that everything is marked correctly. |
| this._changeDetectorRef.markForCheck(); |
| })); |
| ref.released.subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this.released.emit({ source: this }); |
| })); |
| ref.ended.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.ended.emit({ source: this, distance: event.distance }); |
| // Since all of these events run outside of change detection, |
| // we need to ensure that everything is marked correctly. |
| this._changeDetectorRef.markForCheck(); |
| })); |
| ref.entered.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.entered.emit({ |
| container: event.container.data, |
| item: this, |
| currentIndex: event.currentIndex |
| }); |
| })); |
| ref.exited.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.exited.emit({ |
| container: event.container.data, |
| item: this |
| }); |
| })); |
| ref.dropped.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.dropped.emit({ |
| previousIndex: event.previousIndex, |
| currentIndex: event.currentIndex, |
| previousContainer: event.previousContainer.data, |
| container: event.container.data, |
| isPointerOverContainer: event.isPointerOverContainer, |
| item: this, |
| distance: event.distance |
| }); |
| })); |
| } |
| } |
| CdkDrag.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[cdkDrag]', |
| exportAs: 'cdkDrag', |
| host: { |
| 'class': 'cdk-drag', |
| '[class.cdk-drag-disabled]': 'disabled', |
| '[class.cdk-drag-dragging]': '_dragRef.isDragging()', |
| }, |
| providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }] |
| },] }, |
| ]; |
| /** @nocollapse */ |
| CdkDrag.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [CDK_DROP_LIST,] }, { type: Optional }, { type: SkipSelf }] }, |
| { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, |
| { type: NgZone }, |
| { type: ViewContainerRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_CONFIG,] }] }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: DragDrop }, |
| { type: ChangeDetectorRef } |
| ]; |
| CdkDrag.propDecorators = { |
| _handles: [{ type: ContentChildren, args: [CdkDragHandle, { descendants: true },] }], |
| _previewTemplate: [{ type: ContentChild, args: [CdkDragPreview, { static: false },] }], |
| _placeholderTemplate: [{ type: ContentChild, args: [CdkDragPlaceholder, { static: false },] }], |
| data: [{ type: Input, args: ['cdkDragData',] }], |
| lockAxis: [{ type: Input, args: ['cdkDragLockAxis',] }], |
| rootElementSelector: [{ type: Input, args: ['cdkDragRootElement',] }], |
| boundaryElement: [{ type: Input, args: ['cdkDragBoundary',] }], |
| dragStartDelay: [{ type: Input, args: ['cdkDragStartDelay',] }], |
| freeDragPosition: [{ type: Input, args: ['cdkDragFreeDragPosition',] }], |
| disabled: [{ type: Input, args: ['cdkDragDisabled',] }], |
| constrainPosition: [{ type: Input, args: ['cdkDragConstrainPosition',] }], |
| started: [{ type: Output, args: ['cdkDragStarted',] }], |
| released: [{ type: Output, args: ['cdkDragReleased',] }], |
| ended: [{ type: Output, args: ['cdkDragEnded',] }], |
| entered: [{ type: Output, args: ['cdkDragEntered',] }], |
| exited: [{ type: Output, args: ['cdkDragExited',] }], |
| dropped: [{ type: Output, args: ['cdkDragDropped',] }], |
| moved: [{ type: Output, args: ['cdkDragMoved',] }] |
| }; |
| /** |
| * Gets the closest ancestor of an element that matches a selector. |
| * @param {?} element |
| * @param {?} selector |
| * @return {?} |
| */ |
| function getClosestMatchingAncestor(element, selector) { |
| /** @type {?} */ |
| let currentElement = (/** @type {?} */ (element.parentElement)); |
| while (currentElement) { |
| // IE doesn't support `matches` so we have to fall back to `msMatchesSelector`. |
| if (currentElement.matches ? currentElement.matches(selector) : |
| ((/** @type {?} */ (currentElement))).msMatchesSelector(selector)) { |
| return currentElement; |
| } |
| currentElement = currentElement.parentElement; |
| } |
| return null; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList` |
| * elements that are placed inside a `cdkDropListGroup` will be connected to each other |
| * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input |
| * from `cdkDropList`. |
| * @template T |
| */ |
| class CdkDropListGroup { |
| constructor() { |
| /** |
| * Drop lists registered inside the group. |
| */ |
| this._items = new Set(); |
| this._disabled = false; |
| } |
| /** |
| * Whether starting a dragging sequence from inside this group is disabled. |
| * @return {?} |
| */ |
| get disabled() { return this._disabled; } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| this._items.clear(); |
| } |
| } |
| CdkDropListGroup.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[cdkDropListGroup]', |
| exportAs: 'cdkDropListGroup', |
| },] }, |
| ]; |
| CdkDropListGroup.propDecorators = { |
| disabled: [{ type: Input, args: ['cdkDropListGroupDisabled',] }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| /** |
| * Counter used to generate unique ids for drop zones. |
| * @type {?} |
| */ |
| let _uniqueIdCounter$1 = 0; |
| const ɵ0 = undefined; |
| // @breaking-change 8.0.0 `CdkDropList` implements `CdkDropListContainer` for backwards |
| // compatiblity. The implements clause, as well as all the methods that it enforces can |
| // be removed when `CdkDropListContainer` is deleted. |
| /** |
| * Container that wraps a set of draggable items. |
| * @template T |
| */ |
| class CdkDropList { |
| /** |
| * @param {?} element |
| * @param {?} dragDrop |
| * @param {?} _changeDetectorRef |
| * @param {?=} _dir |
| * @param {?=} _group |
| */ |
| constructor(element, dragDrop, _changeDetectorRef, _dir, _group) { |
| this.element = element; |
| this._changeDetectorRef = _changeDetectorRef; |
| this._dir = _dir; |
| this._group = _group; |
| /** |
| * Emits when the list has been destroyed. |
| */ |
| this._destroyed = new Subject(); |
| /** |
| * Other draggable containers that this container is connected to and into which the |
| * container's items can be transferred. Can either be references to other drop containers, |
| * or their unique IDs. |
| */ |
| this.connectedTo = []; |
| /** |
| * Direction in which the list is oriented. |
| */ |
| this.orientation = 'vertical'; |
| /** |
| * Unique ID for the drop zone. Can be used as a reference |
| * in the `connectedTo` of another `CdkDropList`. |
| */ |
| this.id = `cdk-drop-list-${_uniqueIdCounter$1++}`; |
| this._disabled = false; |
| this._sortingDisabled = false; |
| /** |
| * Function that is used to determine whether an item |
| * is allowed to be moved into a drop container. |
| */ |
| this.enterPredicate = (/** |
| * @return {?} |
| */ |
| () => true); |
| /** |
| * Whether to auto-scroll the view when the user moves their pointer close to the edges. |
| */ |
| this.autoScrollDisabled = false; |
| /** |
| * Emits when the user drops an item inside the container. |
| */ |
| this.dropped = new EventEmitter(); |
| /** |
| * Emits when the user has moved a new drag item into this container. |
| */ |
| this.entered = new EventEmitter(); |
| /** |
| * Emits when the user removes an item from the container |
| * by dragging it into another container. |
| */ |
| this.exited = new EventEmitter(); |
| /** |
| * Emits as the user is swapping items while actively dragging. |
| */ |
| this.sorted = new EventEmitter(); |
| this._dropListRef = dragDrop.createDropList(element); |
| this._dropListRef.data = this; |
| this._dropListRef.enterPredicate = (/** |
| * @param {?} drag |
| * @param {?} drop |
| * @return {?} |
| */ |
| (drag, drop) => { |
| return this.enterPredicate(drag.data, drop.data); |
| }); |
| this._syncInputs(this._dropListRef); |
| this._handleEvents(this._dropListRef); |
| CdkDropList._dropLists.push(this); |
| if (_group) { |
| _group._items.add(this); |
| } |
| } |
| /** |
| * Whether starting a dragging sequence from this container is disabled. |
| * @return {?} |
| */ |
| get disabled() { |
| return this._disabled || (!!this._group && this._group.disabled); |
| } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| } |
| /** |
| * Whether sorting within this drop list is disabled. |
| * @return {?} |
| */ |
| get sortingDisabled() { return this._sortingDisabled; } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| set sortingDisabled(value) { |
| this._sortingDisabled = coerceBooleanProperty(value); |
| } |
| /** |
| * @return {?} |
| */ |
| ngAfterContentInit() { |
| this._draggables.changes |
| .pipe(startWith(this._draggables), takeUntil(this._destroyed)) |
| .subscribe((/** |
| * @param {?} items |
| * @return {?} |
| */ |
| (items) => { |
| this._dropListRef.withItems(items.map((/** |
| * @param {?} drag |
| * @return {?} |
| */ |
| drag => drag._dragRef))); |
| })); |
| } |
| /** |
| * @return {?} |
| */ |
| ngOnDestroy() { |
| /** @type {?} */ |
| const index = CdkDropList._dropLists.indexOf(this); |
| if (index > -1) { |
| CdkDropList._dropLists.splice(index, 1); |
| } |
| if (this._group) { |
| this._group._items.delete(this); |
| } |
| this._dropListRef.dispose(); |
| this._destroyed.next(); |
| this._destroyed.complete(); |
| } |
| /** |
| * Starts dragging an item. |
| * @return {?} |
| */ |
| start() { |
| this._dropListRef.start(); |
| } |
| /** |
| * 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. |
| * @return {?} |
| */ |
| drop(item, currentIndex, previousContainer, isPointerOverContainer) { |
| this._dropListRef.drop(item._dragRef, currentIndex, ((/** @type {?} */ (previousContainer)))._dropListRef, isPointerOverContainer); |
| } |
| /** |
| * 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._dropListRef.enter(item._dragRef, pointerX, pointerY); |
| } |
| /** |
| * 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._dropListRef.exit(item._dragRef); |
| } |
| /** |
| * Figures out the index of an item in the container. |
| * @param {?} item Item whose index should be determined. |
| * @return {?} |
| */ |
| getItemIndex(item) { |
| return this._dropListRef.getItemIndex(item._dragRef); |
| } |
| /** |
| * 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) { |
| return this._dropListRef._sortItem(item._dragRef, pointerX, pointerY, pointerDelta); |
| } |
| /** |
| * 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) { |
| /** @type {?} */ |
| const result = this._dropListRef._getSiblingContainerFromPosition(item._dragRef, x, y); |
| return result ? result.data : null; |
| } |
| /** |
| * 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 this._dropListRef._isOverContainer(x, y); |
| } |
| /** |
| * Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. |
| * @private |
| * @param {?} ref |
| * @return {?} |
| */ |
| _syncInputs(ref) { |
| if (this._dir) { |
| this._dir.change |
| .pipe(startWith(this._dir.value), takeUntil(this._destroyed)) |
| .subscribe((/** |
| * @param {?} value |
| * @return {?} |
| */ |
| value => ref.withDirection(value))); |
| } |
| ref.beforeStarted.subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| /** @type {?} */ |
| const siblings = coerceArray(this.connectedTo).map((/** |
| * @param {?} drop |
| * @return {?} |
| */ |
| drop => { |
| return typeof drop === 'string' ? |
| (/** @type {?} */ (CdkDropList._dropLists.find((/** |
| * @param {?} list |
| * @return {?} |
| */ |
| list => list.id === drop)))) : drop; |
| })); |
| if (this._group) { |
| this._group._items.forEach((/** |
| * @param {?} drop |
| * @return {?} |
| */ |
| drop => { |
| if (siblings.indexOf(drop) === -1) { |
| siblings.push(drop); |
| } |
| })); |
| } |
| ref.disabled = this.disabled; |
| ref.lockAxis = this.lockAxis; |
| ref.sortingDisabled = this.sortingDisabled; |
| ref.autoScrollDisabled = this.autoScrollDisabled; |
| ref |
| .connectedTo(siblings.filter((/** |
| * @param {?} drop |
| * @return {?} |
| */ |
| drop => drop && drop !== this)).map((/** |
| * @param {?} list |
| * @return {?} |
| */ |
| list => list._dropListRef))) |
| .withOrientation(this.orientation); |
| })); |
| } |
| /** |
| * Handles events from the underlying DropListRef. |
| * @private |
| * @param {?} ref |
| * @return {?} |
| */ |
| _handleEvents(ref) { |
| ref.beforeStarted.subscribe((/** |
| * @return {?} |
| */ |
| () => { |
| this._changeDetectorRef.markForCheck(); |
| })); |
| ref.entered.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.entered.emit({ |
| container: this, |
| item: event.item.data, |
| currentIndex: event.currentIndex |
| }); |
| })); |
| ref.exited.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.exited.emit({ |
| container: this, |
| item: event.item.data |
| }); |
| this._changeDetectorRef.markForCheck(); |
| })); |
| ref.sorted.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.sorted.emit({ |
| previousIndex: event.previousIndex, |
| currentIndex: event.currentIndex, |
| container: this, |
| item: event.item.data |
| }); |
| })); |
| ref.dropped.subscribe((/** |
| * @param {?} event |
| * @return {?} |
| */ |
| event => { |
| this.dropped.emit({ |
| previousIndex: event.previousIndex, |
| currentIndex: event.currentIndex, |
| previousContainer: event.previousContainer.data, |
| container: event.container.data, |
| item: event.item.data, |
| isPointerOverContainer: event.isPointerOverContainer, |
| distance: event.distance |
| }); |
| // Mark for check since all of these events run outside of change |
| // detection and we're not guaranteed for something else to have triggered it. |
| this._changeDetectorRef.markForCheck(); |
| })); |
| } |
| } |
| /** |
| * Keeps track of the drop lists that are currently on the page. |
| */ |
| CdkDropList._dropLists = []; |
| CdkDropList.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[cdkDropList], cdk-drop-list', |
| exportAs: 'cdkDropList', |
| providers: [ |
| // Prevent child drop lists from picking up the same group as their parent. |
| { provide: CdkDropListGroup, useValue: ɵ0 }, |
| { provide: CDK_DROP_LIST_CONTAINER, useExisting: CdkDropList }, |
| ], |
| host: { |
| 'class': 'cdk-drop-list', |
| '[id]': 'id', |
| '[class.cdk-drop-list-disabled]': 'disabled', |
| '[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()', |
| '[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()', |
| } |
| },] }, |
| ]; |
| /** @nocollapse */ |
| CdkDropList.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: DragDrop }, |
| { type: ChangeDetectorRef }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: CdkDropListGroup, decorators: [{ type: Optional }, { type: SkipSelf }] } |
| ]; |
| CdkDropList.propDecorators = { |
| _draggables: [{ type: ContentChildren, args: [forwardRef((/** |
| * @return {?} |
| */ |
| () => 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 |
| },] }], |
| connectedTo: [{ type: Input, args: ['cdkDropListConnectedTo',] }], |
| data: [{ type: Input, args: ['cdkDropListData',] }], |
| orientation: [{ type: Input, args: ['cdkDropListOrientation',] }], |
| id: [{ type: Input }], |
| lockAxis: [{ type: Input, args: ['cdkDropListLockAxis',] }], |
| disabled: [{ type: Input, args: ['cdkDropListDisabled',] }], |
| sortingDisabled: [{ type: Input, args: ['cdkDropListSortingDisabled',] }], |
| enterPredicate: [{ type: Input, args: ['cdkDropListEnterPredicate',] }], |
| autoScrollDisabled: [{ type: Input, args: ['cdkDropListAutoScrollDisabled',] }], |
| dropped: [{ type: Output, args: ['cdkDropListDropped',] }], |
| entered: [{ type: Output, args: ['cdkDropListEntered',] }], |
| exited: [{ type: Output, args: ['cdkDropListExited',] }], |
| sorted: [{ type: Output, args: ['cdkDropListSorted',] }] |
| }; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| class DragDropModule { |
| } |
| DragDropModule.decorators = [ |
| { type: NgModule, args: [{ |
| declarations: [ |
| CdkDropList, |
| CdkDropListGroup, |
| CdkDrag, |
| CdkDragHandle, |
| CdkDragPreview, |
| CdkDragPlaceholder, |
| ], |
| exports: [ |
| CdkDropList, |
| CdkDropListGroup, |
| CdkDrag, |
| CdkDragHandle, |
| CdkDragPreview, |
| CdkDragPlaceholder, |
| ], |
| providers: [ |
| DragDrop, |
| ] |
| },] }, |
| ]; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc |
| */ |
| |
| export { DragDrop, DragRef, DropListRef, CdkDropList, CDK_DROP_LIST, CDK_DROP_LIST_CONTAINER, moveItemInArray, transferArrayItem, copyArrayItem, DragDropModule, DragDropRegistry, CdkDropListGroup, CDK_DRAG_CONFIG_FACTORY, CDK_DRAG_CONFIG, CdkDrag, CdkDragHandle, CdkDragPreview, CdkDragPlaceholder, CDK_DRAG_PARENT as ɵb }; |
| //# sourceMappingURL=drag-drop.js.map |