source: trip-planner-front/node_modules/@angular/cdk/fesm2015/drag-drop.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 161.2 KB
RevLine 
[6a3a178]1import * as i0 from '@angular/core';
2import { Injectable, NgZone, Inject, InjectionToken, Directive, Input, EventEmitter, ElementRef, ChangeDetectorRef, Optional, SkipSelf, Output, TemplateRef, ViewContainerRef, Self, ContentChildren, ContentChild, NgModule } from '@angular/core';
3import * as i1 from '@angular/common';
4import { DOCUMENT } from '@angular/common';
5import * as i2 from '@angular/cdk/scrolling';
6import { ViewportRuler, ScrollDispatcher, CdkScrollableModule } from '@angular/cdk/scrolling';
7import { _getEventTarget, normalizePassiveListenerOptions, _getShadowRoot } from '@angular/cdk/platform';
8import { coerceBooleanProperty, coerceElement, coerceArray, coerceNumberProperty } from '@angular/cdk/coercion';
9import { isFakeTouchstartFromScreenReader, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y';
10import { Subject, Subscription, interval, animationFrameScheduler, Observable, merge } from 'rxjs';
11import { takeUntil, startWith, map, take, tap, switchMap } from 'rxjs/operators';
12import { Directionality } from '@angular/cdk/bidi';
13
14/**
15 * @license
16 * Copyright Google LLC All Rights Reserved.
17 *
18 * Use of this source code is governed by an MIT-style license that can be
19 * found in the LICENSE file at https://angular.io/license
20 */
21/**
22 * Shallow-extends a stylesheet object with another stylesheet-like object.
23 * Note that the keys in `source` have to be dash-cased.
24 * @docs-private
25 */
26function extendStyles(dest, source, importantProperties) {
27 for (let key in source) {
28 if (source.hasOwnProperty(key)) {
29 const value = source[key];
30 if (value) {
31 dest.setProperty(key, value, (importantProperties === null || importantProperties === void 0 ? void 0 : importantProperties.has(key)) ? 'important' : '');
32 }
33 else {
34 dest.removeProperty(key);
35 }
36 }
37 }
38 return dest;
39}
40/**
41 * Toggles whether the native drag interactions should be enabled for an element.
42 * @param element Element on which to toggle the drag interactions.
43 * @param enable Whether the drag interactions should be enabled.
44 * @docs-private
45 */
46function toggleNativeDragInteractions(element, enable) {
47 const userSelect = enable ? '' : 'none';
48 extendStyles(element.style, {
49 'touch-action': enable ? '' : 'none',
50 '-webkit-user-drag': enable ? '' : 'none',
51 '-webkit-tap-highlight-color': enable ? '' : 'transparent',
52 'user-select': userSelect,
53 '-ms-user-select': userSelect,
54 '-webkit-user-select': userSelect,
55 '-moz-user-select': userSelect
56 });
57}
58/**
59 * Toggles whether an element is visible while preserving its dimensions.
60 * @param element Element whose visibility to toggle
61 * @param enable Whether the element should be visible.
62 * @param importantProperties Properties to be set as `!important`.
63 * @docs-private
64 */
65function toggleVisibility(element, enable, importantProperties) {
66 extendStyles(element.style, {
67 position: enable ? '' : 'fixed',
68 top: enable ? '' : '0',
69 opacity: enable ? '' : '0',
70 left: enable ? '' : '-999em'
71 }, importantProperties);
72}
73/**
74 * Combines a transform string with an optional other transform
75 * that exited before the base transform was applied.
76 */
77function combineTransforms(transform, initialTransform) {
78 return initialTransform && initialTransform != 'none' ?
79 (transform + ' ' + initialTransform) :
80 transform;
81}
82
83/**
84 * @license
85 * Copyright Google LLC All Rights Reserved.
86 *
87 * Use of this source code is governed by an MIT-style license that can be
88 * found in the LICENSE file at https://angular.io/license
89 */
90/** Parses a CSS time value to milliseconds. */
91function parseCssTimeUnitsToMs(value) {
92 // Some browsers will return it in seconds, whereas others will return milliseconds.
93 const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;
94 return parseFloat(value) * multiplier;
95}
96/** Gets the transform transition duration, including the delay, of an element in milliseconds. */
97function getTransformTransitionDurationInMs(element) {
98 const computedStyle = getComputedStyle(element);
99 const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');
100 const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');
101 // If there's no transition for `all` or `transform`, we shouldn't do anything.
102 if (!property) {
103 return 0;
104 }
105 // Get the index of the property that we're interested in and match
106 // it up to the same index in `transition-delay` and `transition-duration`.
107 const propertyIndex = transitionedProperties.indexOf(property);
108 const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');
109 const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');
110 return parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +
111 parseCssTimeUnitsToMs(rawDelays[propertyIndex]);
112}
113/** Parses out multiple values from a computed style into an array. */
114function parseCssPropertyValue(computedStyle, name) {
115 const value = computedStyle.getPropertyValue(name);
116 return value.split(',').map(part => part.trim());
117}
118
119/**
120 * @license
121 * Copyright Google LLC All Rights Reserved.
122 *
123 * Use of this source code is governed by an MIT-style license that can be
124 * found in the LICENSE file at https://angular.io/license
125 */
126/** Gets a mutable version of an element's bounding `ClientRect`. */
127function getMutableClientRect(element) {
128 const clientRect = element.getBoundingClientRect();
129 // We need to clone the `clientRect` here, because all the values on it are readonly
130 // and we need to be able to update them. Also we can't use a spread here, because
131 // the values on a `ClientRect` aren't own properties. See:
132 // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
133 return {
134 top: clientRect.top,
135 right: clientRect.right,
136 bottom: clientRect.bottom,
137 left: clientRect.left,
138 width: clientRect.width,
139 height: clientRect.height
140 };
141}
142/**
143 * Checks whether some coordinates are within a `ClientRect`.
144 * @param clientRect ClientRect that is being checked.
145 * @param x Coordinates along the X axis.
146 * @param y Coordinates along the Y axis.
147 */
148function isInsideClientRect(clientRect, x, y) {
149 const { top, bottom, left, right } = clientRect;
150 return y >= top && y <= bottom && x >= left && x <= right;
151}
152/**
153 * Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
154 * @param clientRect `ClientRect` that should be updated.
155 * @param top Amount to add to the `top` position.
156 * @param left Amount to add to the `left` position.
157 */
158function adjustClientRect(clientRect, top, left) {
159 clientRect.top += top;
160 clientRect.bottom = clientRect.top + clientRect.height;
161 clientRect.left += left;
162 clientRect.right = clientRect.left + clientRect.width;
163}
164/**
165 * Checks whether the pointer coordinates are close to a ClientRect.
166 * @param rect ClientRect to check against.
167 * @param threshold Threshold around the ClientRect.
168 * @param pointerX Coordinates along the X axis.
169 * @param pointerY Coordinates along the Y axis.
170 */
171function isPointerNearClientRect(rect, threshold, pointerX, pointerY) {
172 const { top, right, bottom, left, width, height } = rect;
173 const xThreshold = width * threshold;
174 const yThreshold = height * threshold;
175 return pointerY > top - yThreshold && pointerY < bottom + yThreshold &&
176 pointerX > left - xThreshold && pointerX < right + xThreshold;
177}
178
179/**
180 * @license
181 * Copyright Google LLC All Rights Reserved.
182 *
183 * Use of this source code is governed by an MIT-style license that can be
184 * found in the LICENSE file at https://angular.io/license
185 */
186/** Keeps track of the scroll position and dimensions of the parents of an element. */
187class ParentPositionTracker {
188 constructor(_document, _viewportRuler) {
189 this._document = _document;
190 this._viewportRuler = _viewportRuler;
191 /** Cached positions of the scrollable parent elements. */
192 this.positions = new Map();
193 }
194 /** Clears the cached positions. */
195 clear() {
196 this.positions.clear();
197 }
198 /** Caches the positions. Should be called at the beginning of a drag sequence. */
199 cache(elements) {
200 this.clear();
201 this.positions.set(this._document, {
202 scrollPosition: this._viewportRuler.getViewportScrollPosition(),
203 });
204 elements.forEach(element => {
205 this.positions.set(element, {
206 scrollPosition: { top: element.scrollTop, left: element.scrollLeft },
207 clientRect: getMutableClientRect(element)
208 });
209 });
210 }
211 /** Handles scrolling while a drag is taking place. */
212 handleScroll(event) {
213 const target = _getEventTarget(event);
214 const cachedPosition = this.positions.get(target);
215 if (!cachedPosition) {
216 return null;
217 }
218 // Used when figuring out whether an element is inside the scroll parent. If the scrolled
219 // parent is the `document`, we use the `documentElement`, because IE doesn't support
220 // `contains` on the `document`.
221 const scrolledParentNode = target === this._document ? target.documentElement : target;
222 const scrollPosition = cachedPosition.scrollPosition;
223 let newTop;
224 let newLeft;
225 if (target === this._document) {
226 const viewportScrollPosition = this._viewportRuler.getViewportScrollPosition();
227 newTop = viewportScrollPosition.top;
228 newLeft = viewportScrollPosition.left;
229 }
230 else {
231 newTop = target.scrollTop;
232 newLeft = target.scrollLeft;
233 }
234 const topDifference = scrollPosition.top - newTop;
235 const leftDifference = scrollPosition.left - newLeft;
236 // Go through and update the cached positions of the scroll
237 // parents that are inside the element that was scrolled.
238 this.positions.forEach((position, node) => {
239 if (position.clientRect && target !== node && scrolledParentNode.contains(node)) {
240 adjustClientRect(position.clientRect, topDifference, leftDifference);
241 }
242 });
243 scrollPosition.top = newTop;
244 scrollPosition.left = newLeft;
245 return { top: topDifference, left: leftDifference };
246 }
247}
248
249/**
250 * @license
251 * Copyright Google LLC All Rights Reserved.
252 *
253 * Use of this source code is governed by an MIT-style license that can be
254 * found in the LICENSE file at https://angular.io/license
255 */
256/** Creates a deep clone of an element. */
257function deepCloneNode(node) {
258 const clone = node.cloneNode(true);
259 const descendantsWithId = clone.querySelectorAll('[id]');
260 const nodeName = node.nodeName.toLowerCase();
261 // Remove the `id` to avoid having multiple elements with the same id on the page.
262 clone.removeAttribute('id');
263 for (let i = 0; i < descendantsWithId.length; i++) {
264 descendantsWithId[i].removeAttribute('id');
265 }
266 if (nodeName === 'canvas') {
267 transferCanvasData(node, clone);
268 }
269 else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') {
270 transferInputData(node, clone);
271 }
272 transferData('canvas', node, clone, transferCanvasData);
273 transferData('input, textarea, select', node, clone, transferInputData);
274 return clone;
275}
276/** Matches elements between an element and its clone and allows for their data to be cloned. */
277function transferData(selector, node, clone, callback) {
278 const descendantElements = node.querySelectorAll(selector);
279 if (descendantElements.length) {
280 const cloneElements = clone.querySelectorAll(selector);
281 for (let i = 0; i < descendantElements.length; i++) {
282 callback(descendantElements[i], cloneElements[i]);
283 }
284 }
285}
286// Counter for unique cloned radio button names.
287let cloneUniqueId = 0;
288/** Transfers the data of one input element to another. */
289function transferInputData(source, clone) {
290 // Browsers throw an error when assigning the value of a file input programmatically.
291 if (clone.type !== 'file') {
292 clone.value = source.value;
293 }
294 // Radio button `name` attributes must be unique for radio button groups
295 // otherwise original radio buttons can lose their checked state
296 // once the clone is inserted in the DOM.
297 if (clone.type === 'radio' && clone.name) {
298 clone.name = `mat-clone-${clone.name}-${cloneUniqueId++}`;
299 }
300}
301/** Transfers the data of one canvas element to another. */
302function transferCanvasData(source, clone) {
303 const context = clone.getContext('2d');
304 if (context) {
305 // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0).
306 // We can't do much about it so just ignore the error.
307 try {
308 context.drawImage(source, 0, 0);
309 }
310 catch (_a) { }
311 }
312}
313
314/**
315 * @license
316 * Copyright Google LLC All Rights Reserved.
317 *
318 * Use of this source code is governed by an MIT-style license that can be
319 * found in the LICENSE file at https://angular.io/license
320 */
321/** Options that can be used to bind a passive event listener. */
322const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
323/** Options that can be used to bind an active event listener. */
324const activeEventListenerOptions = normalizePassiveListenerOptions({ passive: false });
325/**
326 * Time in milliseconds for which to ignore mouse events, after
327 * receiving a touch event. Used to avoid doing double work for
328 * touch devices where the browser fires fake mouse events, in
329 * addition to touch events.
330 */
331const MOUSE_EVENT_IGNORE_TIME = 800;
332/** Inline styles to be set as `!important` while dragging. */
333const dragImportantProperties = new Set([
334 // Needs to be important, because some `mat-table` sets `position: sticky !important`. See #22781.
335 'position'
336]);
337/**
338 * Reference to a draggable item. Used to manipulate or dispose of the item.
339 */
340class DragRef {
341 constructor(element, _config, _document, _ngZone, _viewportRuler, _dragDropRegistry) {
342 this._config = _config;
343 this._document = _document;
344 this._ngZone = _ngZone;
345 this._viewportRuler = _viewportRuler;
346 this._dragDropRegistry = _dragDropRegistry;
347 /**
348 * CSS `transform` applied to the element when it isn't being dragged. We need a
349 * passive transform in order for the dragged element to retain its new position
350 * after the user has stopped dragging and because we need to know the relative
351 * position in case they start dragging again. This corresponds to `element.style.transform`.
352 */
353 this._passiveTransform = { x: 0, y: 0 };
354 /** CSS `transform` that is applied to the element while it's being dragged. */
355 this._activeTransform = { x: 0, y: 0 };
356 /**
357 * Whether the dragging sequence has been started. Doesn't
358 * necessarily mean that the element has been moved.
359 */
360 this._hasStartedDragging = false;
361 /** Emits when the item is being moved. */
362 this._moveEvents = new Subject();
363 /** Subscription to pointer movement events. */
364 this._pointerMoveSubscription = Subscription.EMPTY;
365 /** Subscription to the event that is dispatched when the user lifts their pointer. */
366 this._pointerUpSubscription = Subscription.EMPTY;
367 /** Subscription to the viewport being scrolled. */
368 this._scrollSubscription = Subscription.EMPTY;
369 /** Subscription to the viewport being resized. */
370 this._resizeSubscription = Subscription.EMPTY;
371 /** Cached reference to the boundary element. */
372 this._boundaryElement = null;
373 /** Whether the native dragging interactions have been enabled on the root element. */
374 this._nativeInteractionsEnabled = true;
375 /** Elements that can be used to drag the draggable item. */
376 this._handles = [];
377 /** Registered handles that are currently disabled. */
378 this._disabledHandles = new Set();
379 /** Layout direction of the item. */
380 this._direction = 'ltr';
381 /**
382 * Amount of milliseconds to wait after the user has put their
383 * pointer down before starting to drag the element.
384 */
385 this.dragStartDelay = 0;
386 this._disabled = false;
387 /** Emits as the drag sequence is being prepared. */
388 this.beforeStarted = new Subject();
389 /** Emits when the user starts dragging the item. */
390 this.started = new Subject();
391 /** Emits when the user has released a drag item, before any animations have started. */
392 this.released = new Subject();
393 /** Emits when the user stops dragging an item in the container. */
394 this.ended = new Subject();
395 /** Emits when the user has moved the item into a new container. */
396 this.entered = new Subject();
397 /** Emits when the user removes the item its container by dragging it into another container. */
398 this.exited = new Subject();
399 /** Emits when the user drops the item inside a container. */
400 this.dropped = new Subject();
401 /**
402 * Emits as the user is dragging the item. Use with caution,
403 * because this event will fire for every pixel that the user has dragged.
404 */
405 this.moved = this._moveEvents;
406 /** Handler for the `mousedown`/`touchstart` events. */
407 this._pointerDown = (event) => {
408 this.beforeStarted.next();
409 // Delegate the event based on whether it started from a handle or the element itself.
410 if (this._handles.length) {
411 const targetHandle = this._handles.find(handle => {
412 const target = _getEventTarget(event);
413 return !!target && (target === handle || handle.contains(target));
414 });
415 if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
416 this._initializeDragSequence(targetHandle, event);
417 }
418 }
419 else if (!this.disabled) {
420 this._initializeDragSequence(this._rootElement, event);
421 }
422 };
423 /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */
424 this._pointerMove = (event) => {
425 const pointerPosition = this._getPointerPositionOnPage(event);
426 if (!this._hasStartedDragging) {
427 const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
428 const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
429 const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
430 // Only start dragging after the user has moved more than the minimum distance in either
431 // direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
432 // in the `pointerMove` subscription, because we're not guaranteed to have one move event
433 // per pixel of movement (e.g. if the user moves their pointer quickly).
434 if (isOverThreshold) {
435 const isDelayElapsed = Date.now() >= this._dragStartTime + this._getDragStartDelay(event);
436 const container = this._dropContainer;
437 if (!isDelayElapsed) {
438 this._endDragSequence(event);
439 return;
440 }
441 // Prevent other drag sequences from starting while something in the container is still
442 // being dragged. This can happen while we're waiting for the drop animation to finish
443 // and can cause errors, because some elements might still be moving around.
444 if (!container || (!container.isDragging() && !container.isReceiving())) {
445 // Prevent the default action as soon as the dragging sequence is considered as
446 // "started" since waiting for the next event can allow the device to begin scrolling.
447 event.preventDefault();
448 this._hasStartedDragging = true;
449 this._ngZone.run(() => this._startDragSequence(event));
450 }
451 }
452 return;
453 }
454 // We only need the preview dimensions if we have a boundary element.
455 if (this._boundaryElement) {
456 // Cache the preview element rect if we haven't cached it already or if
457 // we cached it too early before the element dimensions were computed.
458 if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) {
459 this._previewRect = (this._preview || this._rootElement).getBoundingClientRect();
460 }
461 }
462 // We prevent the default action down here so that we know that dragging has started. This is
463 // important for touch devices where doing this too early can unnecessarily block scrolling,
464 // if there's a dragging delay.
465 event.preventDefault();
466 const constrainedPointerPosition = this._getConstrainedPointerPosition(pointerPosition);
467 this._hasMoved = true;
468 this._lastKnownPointerPosition = pointerPosition;
469 this._updatePointerDirectionDelta(constrainedPointerPosition);
470 if (this._dropContainer) {
471 this._updateActiveDropContainer(constrainedPointerPosition, pointerPosition);
472 }
473 else {
474 const activeTransform = this._activeTransform;
475 activeTransform.x =
476 constrainedPointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x;
477 activeTransform.y =
478 constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;
479 this._applyRootElementTransform(activeTransform.x, activeTransform.y);
480 // Apply transform as attribute if dragging and svg element to work for IE
481 if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
482 const appliedTransform = `translate(${activeTransform.x} ${activeTransform.y})`;
483 this._rootElement.setAttribute('transform', appliedTransform);
484 }
485 }
486 // Since this event gets fired for every pixel while dragging, we only
487 // want to fire it if the consumer opted into it. Also we have to
488 // re-enter the zone because we run all of the events on the outside.
489 if (this._moveEvents.observers.length) {
490 this._ngZone.run(() => {
491 this._moveEvents.next({
492 source: this,
493 pointerPosition: constrainedPointerPosition,
494 event,
495 distance: this._getDragDistance(constrainedPointerPosition),
496 delta: this._pointerDirectionDelta
497 });
498 });
499 }
500 };
501 /** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */
502 this._pointerUp = (event) => {
503 this._endDragSequence(event);
504 };
505 this.withRootElement(element).withParent(_config.parentDragRef || null);
506 this._parentPositions = new ParentPositionTracker(_document, _viewportRuler);
507 _dragDropRegistry.registerDragItem(this);
508 }
509 /** Whether starting to drag this element is disabled. */
510 get disabled() {
511 return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
512 }
513 set disabled(value) {
514 const newValue = coerceBooleanProperty(value);
515 if (newValue !== this._disabled) {
516 this._disabled = newValue;
517 this._toggleNativeDragInteractions();
518 this._handles.forEach(handle => toggleNativeDragInteractions(handle, newValue));
519 }
520 }
521 /**
522 * Returns the element that is being used as a placeholder
523 * while the current element is being dragged.
524 */
525 getPlaceholderElement() {
526 return this._placeholder;
527 }
528 /** Returns the root draggable element. */
529 getRootElement() {
530 return this._rootElement;
531 }
532 /**
533 * Gets the currently-visible element that represents the drag item.
534 * While dragging this is the placeholder, otherwise it's the root element.
535 */
536 getVisibleElement() {
537 return this.isDragging() ? this.getPlaceholderElement() : this.getRootElement();
538 }
539 /** Registers the handles that can be used to drag the element. */
540 withHandles(handles) {
541 this._handles = handles.map(handle => coerceElement(handle));
542 this._handles.forEach(handle => toggleNativeDragInteractions(handle, this.disabled));
543 this._toggleNativeDragInteractions();
544 // Delete any lingering disabled handles that may have been destroyed. Note that we re-create
545 // the set, rather than iterate over it and filter out the destroyed handles, because while
546 // the ES spec allows for sets to be modified while they're being iterated over, some polyfills
547 // use an array internally which may throw an error.
548 const disabledHandles = new Set();
549 this._disabledHandles.forEach(handle => {
550 if (this._handles.indexOf(handle) > -1) {
551 disabledHandles.add(handle);
552 }
553 });
554 this._disabledHandles = disabledHandles;
555 return this;
556 }
557 /**
558 * Registers the template that should be used for the drag preview.
559 * @param template Template that from which to stamp out the preview.
560 */
561 withPreviewTemplate(template) {
562 this._previewTemplate = template;
563 return this;
564 }
565 /**
566 * Registers the template that should be used for the drag placeholder.
567 * @param template Template that from which to stamp out the placeholder.
568 */
569 withPlaceholderTemplate(template) {
570 this._placeholderTemplate = template;
571 return this;
572 }
573 /**
574 * Sets an alternate drag root element. The root element is the element that will be moved as
575 * the user is dragging. Passing an alternate root element is useful when trying to enable
576 * dragging on an element that you might not have access to.
577 */
578 withRootElement(rootElement) {
579 const element = coerceElement(rootElement);
580 if (element !== this._rootElement) {
581 if (this._rootElement) {
582 this._removeRootElementListeners(this._rootElement);
583 }
584 this._ngZone.runOutsideAngular(() => {
585 element.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
586 element.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
587 });
588 this._initialTransform = undefined;
589 this._rootElement = element;
590 }
591 if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
592 this._ownerSVGElement = this._rootElement.ownerSVGElement;
593 }
594 return this;
595 }
596 /**
597 * Element to which the draggable's position will be constrained.
598 */
599 withBoundaryElement(boundaryElement) {
600 this._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
601 this._resizeSubscription.unsubscribe();
602 if (boundaryElement) {
603 this._resizeSubscription = this._viewportRuler
604 .change(10)
605 .subscribe(() => this._containInsideBoundaryOnResize());
606 }
607 return this;
608 }
609 /** Sets the parent ref that the ref is nested in. */
610 withParent(parent) {
611 this._parentDragRef = parent;
612 return this;
613 }
614 /** Removes the dragging functionality from the DOM element. */
615 dispose() {
616 this._removeRootElementListeners(this._rootElement);
617 // Do this check before removing from the registry since it'll
618 // stop being considered as dragged once it is removed.
619 if (this.isDragging()) {
620 // Since we move out the element to the end of the body while it's being
621 // dragged, we have to make sure that it's removed if it gets destroyed.
622 removeNode(this._rootElement);
623 }
624 removeNode(this._anchor);
625 this._destroyPreview();
626 this._destroyPlaceholder();
627 this._dragDropRegistry.removeDragItem(this);
628 this._removeSubscriptions();
629 this.beforeStarted.complete();
630 this.started.complete();
631 this.released.complete();
632 this.ended.complete();
633 this.entered.complete();
634 this.exited.complete();
635 this.dropped.complete();
636 this._moveEvents.complete();
637 this._handles = [];
638 this._disabledHandles.clear();
639 this._dropContainer = undefined;
640 this._resizeSubscription.unsubscribe();
641 this._parentPositions.clear();
642 this._boundaryElement = this._rootElement = this._ownerSVGElement = this._placeholderTemplate =
643 this._previewTemplate = this._anchor = this._parentDragRef = null;
644 }
645 /** Checks whether the element is currently being dragged. */
646 isDragging() {
647 return this._hasStartedDragging && this._dragDropRegistry.isDragging(this);
648 }
649 /** Resets a standalone drag item to its initial position. */
650 reset() {
651 this._rootElement.style.transform = this._initialTransform || '';
652 this._activeTransform = { x: 0, y: 0 };
653 this._passiveTransform = { x: 0, y: 0 };
654 }
655 /**
656 * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.
657 * @param handle Handle element that should be disabled.
658 */
659 disableHandle(handle) {
660 if (!this._disabledHandles.has(handle) && this._handles.indexOf(handle) > -1) {
661 this._disabledHandles.add(handle);
662 toggleNativeDragInteractions(handle, true);
663 }
664 }
665 /**
666 * Enables a handle, if it has been disabled.
667 * @param handle Handle element to be enabled.
668 */
669 enableHandle(handle) {
670 if (this._disabledHandles.has(handle)) {
671 this._disabledHandles.delete(handle);
672 toggleNativeDragInteractions(handle, this.disabled);
673 }
674 }
675 /** Sets the layout direction of the draggable item. */
676 withDirection(direction) {
677 this._direction = direction;
678 return this;
679 }
680 /** Sets the container that the item is part of. */
681 _withDropContainer(container) {
682 this._dropContainer = container;
683 }
684 /**
685 * Gets the current position in pixels the draggable outside of a drop container.
686 */
687 getFreeDragPosition() {
688 const position = this.isDragging() ? this._activeTransform : this._passiveTransform;
689 return { x: position.x, y: position.y };
690 }
691 /**
692 * Sets the current position in pixels the draggable outside of a drop container.
693 * @param value New position to be set.
694 */
695 setFreeDragPosition(value) {
696 this._activeTransform = { x: 0, y: 0 };
697 this._passiveTransform.x = value.x;
698 this._passiveTransform.y = value.y;
699 if (!this._dropContainer) {
700 this._applyRootElementTransform(value.x, value.y);
701 }
702 return this;
703 }
704 /**
705 * Sets the container into which to insert the preview element.
706 * @param value Container into which to insert the preview.
707 */
708 withPreviewContainer(value) {
709 this._previewContainer = value;
710 return this;
711 }
712 /** Updates the item's sort order based on the last-known pointer position. */
713 _sortFromLastPointerPosition() {
714 const position = this._lastKnownPointerPosition;
715 if (position && this._dropContainer) {
716 this._updateActiveDropContainer(this._getConstrainedPointerPosition(position), position);
717 }
718 }
719 /** Unsubscribes from the global subscriptions. */
720 _removeSubscriptions() {
721 this._pointerMoveSubscription.unsubscribe();
722 this._pointerUpSubscription.unsubscribe();
723 this._scrollSubscription.unsubscribe();
724 }
725 /** Destroys the preview element and its ViewRef. */
726 _destroyPreview() {
727 if (this._preview) {
728 removeNode(this._preview);
729 }
730 if (this._previewRef) {
731 this._previewRef.destroy();
732 }
733 this._preview = this._previewRef = null;
734 }
735 /** Destroys the placeholder element and its ViewRef. */
736 _destroyPlaceholder() {
737 if (this._placeholder) {
738 removeNode(this._placeholder);
739 }
740 if (this._placeholderRef) {
741 this._placeholderRef.destroy();
742 }
743 this._placeholder = this._placeholderRef = null;
744 }
745 /**
746 * Clears subscriptions and stops the dragging sequence.
747 * @param event Browser event object that ended the sequence.
748 */
749 _endDragSequence(event) {
750 // Note that here we use `isDragging` from the service, rather than from `this`.
751 // The difference is that the one from the service reflects whether a dragging sequence
752 // has been initiated, whereas the one on `this` includes whether the user has passed
753 // the minimum dragging threshold.
754 if (!this._dragDropRegistry.isDragging(this)) {
755 return;
756 }
757 this._removeSubscriptions();
758 this._dragDropRegistry.stopDragging(this);
759 this._toggleNativeDragInteractions();
760 if (this._handles) {
761 this._rootElement.style.webkitTapHighlightColor = this._rootElementTapHighlight;
762 }
763 if (!this._hasStartedDragging) {
764 return;
765 }
766 this.released.next({ source: this });
767 if (this._dropContainer) {
768 // Stop scrolling immediately, instead of waiting for the animation to finish.
769 this._dropContainer._stopScrolling();
770 this._animatePreviewToPlaceholder().then(() => {
771 this._cleanupDragArtifacts(event);
772 this._cleanupCachedDimensions();
773 this._dragDropRegistry.stopDragging(this);
774 });
775 }
776 else {
777 // Convert the active transform into a passive one. This means that next time
778 // the user starts dragging the item, its position will be calculated relatively
779 // to the new passive transform.
780 this._passiveTransform.x = this._activeTransform.x;
781 const pointerPosition = this._getPointerPositionOnPage(event);
782 this._passiveTransform.y = this._activeTransform.y;
783 this._ngZone.run(() => {
784 this.ended.next({
785 source: this,
786 distance: this._getDragDistance(pointerPosition),
787 dropPoint: pointerPosition
788 });
789 });
790 this._cleanupCachedDimensions();
791 this._dragDropRegistry.stopDragging(this);
792 }
793 }
794 /** Starts the dragging sequence. */
795 _startDragSequence(event) {
796 if (isTouchEvent(event)) {
797 this._lastTouchEventTime = Date.now();
798 }
799 this._toggleNativeDragInteractions();
800 const dropContainer = this._dropContainer;
801 if (dropContainer) {
802 const element = this._rootElement;
803 const parent = element.parentNode;
804 const placeholder = this._placeholder = this._createPlaceholderElement();
805 const anchor = this._anchor = this._anchor || this._document.createComment('');
806 // Needs to happen before the root element is moved.
807 const shadowRoot = this._getShadowRoot();
808 // Insert an anchor node so that we can restore the element's position in the DOM.
809 parent.insertBefore(anchor, element);
810 // There's no risk of transforms stacking when inside a drop container so
811 // we can keep the initial transform up to date any time dragging starts.
812 this._initialTransform = element.style.transform || '';
813 // Create the preview after the initial transform has
814 // been cached, because it can be affected by the transform.
815 this._preview = this._createPreviewElement();
816 // We move the element out at the end of the body and we make it hidden, because keeping it in
817 // place will throw off the consumer's `:last-child` selectors. We can't remove the element
818 // from the DOM completely, because iOS will stop firing all subsequent events in the chain.
819 toggleVisibility(element, false, dragImportantProperties);
820 this._document.body.appendChild(parent.replaceChild(placeholder, element));
821 this._getPreviewInsertionPoint(parent, shadowRoot).appendChild(this._preview);
822 this.started.next({ source: this }); // Emit before notifying the container.
823 dropContainer.start();
824 this._initialContainer = dropContainer;
825 this._initialIndex = dropContainer.getItemIndex(this);
826 }
827 else {
828 this.started.next({ source: this });
829 this._initialContainer = this._initialIndex = undefined;
830 }
831 // Important to run after we've called `start` on the parent container
832 // so that it has had time to resolve its scrollable parents.
833 this._parentPositions.cache(dropContainer ? dropContainer.getScrollableParents() : []);
834 }
835 /**
836 * Sets up the different variables and subscriptions
837 * that will be necessary for the dragging sequence.
838 * @param referenceElement Element that started the drag sequence.
839 * @param event Browser event object that started the sequence.
840 */
841 _initializeDragSequence(referenceElement, event) {
842 // Stop propagation if the item is inside another
843 // draggable so we don't start multiple drag sequences.
844 if (this._parentDragRef) {
845 event.stopPropagation();
846 }
847 const isDragging = this.isDragging();
848 const isTouchSequence = isTouchEvent(event);
849 const isAuxiliaryMouseButton = !isTouchSequence && event.button !== 0;
850 const rootElement = this._rootElement;
851 const target = _getEventTarget(event);
852 const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime &&
853 this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
854 const isFakeEvent = isTouchSequence ? isFakeTouchstartFromScreenReader(event) :
855 isFakeMousedownFromScreenReader(event);
856 // If the event started from an element with the native HTML drag&drop, it'll interfere
857 // with our own dragging (e.g. `img` tags do it by default). Prevent the default action
858 // to stop it from happening. Note that preventing on `dragstart` also seems to work, but
859 // it's flaky and it fails if the user drags it away quickly. Also note that we only want
860 // to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
861 // events from firing on touch devices.
862 if (target && target.draggable && event.type === 'mousedown') {
863 event.preventDefault();
864 }
865 // Abort if the user is already dragging or is using a mouse button other than the primary one.
866 if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent || isFakeEvent) {
867 return;
868 }
869 // If we've got handles, we need to disable the tap highlight on the entire root element,
870 // otherwise iOS will still add it, even though all the drag interactions on the handle
871 // are disabled.
872 if (this._handles.length) {
873 this._rootElementTapHighlight = rootElement.style.webkitTapHighlightColor || '';
874 rootElement.style.webkitTapHighlightColor = 'transparent';
875 }
876 this._hasStartedDragging = this._hasMoved = false;
877 // Avoid multiple subscriptions and memory leaks when multi touch
878 // (isDragging check above isn't enough because of possible temporal and/or dimensional delays)
879 this._removeSubscriptions();
880 this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
881 this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
882 this._scrollSubscription = this._dragDropRegistry
883 .scrolled(this._getShadowRoot())
884 .subscribe(scrollEvent => this._updateOnScroll(scrollEvent));
885 if (this._boundaryElement) {
886 this._boundaryRect = getMutableClientRect(this._boundaryElement);
887 }
888 // If we have a custom preview we can't know ahead of time how large it'll be so we position
889 // it next to the cursor. The exception is when the consumer has opted into making the preview
890 // the same size as the root element, in which case we do know the size.
891 const previewTemplate = this._previewTemplate;
892 this._pickupPositionInElement = previewTemplate && previewTemplate.template &&
893 !previewTemplate.matchSize ? { x: 0, y: 0 } :
894 this._getPointerPositionInElement(referenceElement, event);
895 const pointerPosition = this._pickupPositionOnPage = this._lastKnownPointerPosition =
896 this._getPointerPositionOnPage(event);
897 this._pointerDirectionDelta = { x: 0, y: 0 };
898 this._pointerPositionAtLastDirectionChange = { x: pointerPosition.x, y: pointerPosition.y };
899 this._dragStartTime = Date.now();
900 this._dragDropRegistry.startDragging(this, event);
901 }
902 /** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */
903 _cleanupDragArtifacts(event) {
904 // Restore the element's visibility and insert it at its old position in the DOM.
905 // It's important that we maintain the position, because moving the element around in the DOM
906 // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
907 // while moving the existing elements in all other cases.
908 toggleVisibility(this._rootElement, true, dragImportantProperties);
909 this._anchor.parentNode.replaceChild(this._rootElement, this._anchor);
910 this._destroyPreview();
911 this._destroyPlaceholder();
912 this._boundaryRect = this._previewRect = this._initialTransform = undefined;
913 // Re-enter the NgZone since we bound `document` events on the outside.
914 this._ngZone.run(() => {
915 const container = this._dropContainer;
916 const currentIndex = container.getItemIndex(this);
917 const pointerPosition = this._getPointerPositionOnPage(event);
918 const distance = this._getDragDistance(pointerPosition);
919 const isPointerOverContainer = container._isOverContainer(pointerPosition.x, pointerPosition.y);
920 this.ended.next({ source: this, distance, dropPoint: pointerPosition });
921 this.dropped.next({
922 item: this,
923 currentIndex,
924 previousIndex: this._initialIndex,
925 container: container,
926 previousContainer: this._initialContainer,
927 isPointerOverContainer,
928 distance,
929 dropPoint: pointerPosition
930 });
931 container.drop(this, currentIndex, this._initialIndex, this._initialContainer, isPointerOverContainer, distance, pointerPosition);
932 this._dropContainer = this._initialContainer;
933 });
934 }
935 /**
936 * Updates the item's position in its drop container, or moves it
937 * into a new one, depending on its current drag position.
938 */
939 _updateActiveDropContainer({ x, y }, { x: rawX, y: rawY }) {
940 // Drop container that draggable has been moved into.
941 let newContainer = this._initialContainer._getSiblingContainerFromPosition(this, x, y);
942 // If we couldn't find a new container to move the item into, and the item has left its
943 // initial container, check whether the it's over the initial container. This handles the
944 // case where two containers are connected one way and the user tries to undo dragging an
945 // item into a new container.
946 if (!newContainer && this._dropContainer !== this._initialContainer &&
947 this._initialContainer._isOverContainer(x, y)) {
948 newContainer = this._initialContainer;
949 }
950 if (newContainer && newContainer !== this._dropContainer) {
951 this._ngZone.run(() => {
952 // Notify the old container that the item has left.
953 this.exited.next({ item: this, container: this._dropContainer });
954 this._dropContainer.exit(this);
955 // Notify the new container that the item has entered.
956 this._dropContainer = newContainer;
957 this._dropContainer.enter(this, x, y, newContainer === this._initialContainer &&
958 // If we're re-entering the initial container and sorting is disabled,
959 // put item the into its starting index to begin with.
960 newContainer.sortingDisabled ? this._initialIndex : undefined);
961 this.entered.next({
962 item: this,
963 container: newContainer,
964 currentIndex: newContainer.getItemIndex(this)
965 });
966 });
967 }
968 // Dragging may have been interrupted as a result of the events above.
969 if (this.isDragging()) {
970 this._dropContainer._startScrollingIfNecessary(rawX, rawY);
971 this._dropContainer._sortItem(this, x, y, this._pointerDirectionDelta);
972 this._applyPreviewTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
973 }
974 }
975 /**
976 * Creates the element that will be rendered next to the user's pointer
977 * and will be used as a preview of the element that is being dragged.
978 */
979 _createPreviewElement() {
980 const previewConfig = this._previewTemplate;
981 const previewClass = this.previewClass;
982 const previewTemplate = previewConfig ? previewConfig.template : null;
983 let preview;
984 if (previewTemplate && previewConfig) {
985 // Measure the element before we've inserted the preview
986 // since the insertion could throw off the measurement.
987 const rootRect = previewConfig.matchSize ? this._rootElement.getBoundingClientRect() : null;
988 const viewRef = previewConfig.viewContainer.createEmbeddedView(previewTemplate, previewConfig.context);
989 viewRef.detectChanges();
990 preview = getRootNode(viewRef, this._document);
991 this._previewRef = viewRef;
992 if (previewConfig.matchSize) {
993 matchElementSize(preview, rootRect);
994 }
995 else {
996 preview.style.transform =
997 getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
998 }
999 }
1000 else {
1001 const element = this._rootElement;
1002 preview = deepCloneNode(element);
1003 matchElementSize(preview, element.getBoundingClientRect());
1004 if (this._initialTransform) {
1005 preview.style.transform = this._initialTransform;
1006 }
1007 }
1008 extendStyles(preview.style, {
1009 // It's important that we disable the pointer events on the preview, because
1010 // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
1011 'pointer-events': 'none',
1012 // We have to reset the margin, because it can throw off positioning relative to the viewport.
1013 'margin': '0',
1014 'position': 'fixed',
1015 'top': '0',
1016 'left': '0',
1017 'z-index': `${this._config.zIndex || 1000}`
1018 }, dragImportantProperties);
1019 toggleNativeDragInteractions(preview, false);
1020 preview.classList.add('cdk-drag-preview');
1021 preview.setAttribute('dir', this._direction);
1022 if (previewClass) {
1023 if (Array.isArray(previewClass)) {
1024 previewClass.forEach(className => preview.classList.add(className));
1025 }
1026 else {
1027 preview.classList.add(previewClass);
1028 }
1029 }
1030 return preview;
1031 }
1032 /**
1033 * Animates the preview element from its current position to the location of the drop placeholder.
1034 * @returns Promise that resolves when the animation completes.
1035 */
1036 _animatePreviewToPlaceholder() {
1037 // If the user hasn't moved yet, the transitionend event won't fire.
1038 if (!this._hasMoved) {
1039 return Promise.resolve();
1040 }
1041 const placeholderRect = this._placeholder.getBoundingClientRect();
1042 // Apply the class that adds a transition to the preview.
1043 this._preview.classList.add('cdk-drag-animating');
1044 // Move the preview to the placeholder position.
1045 this._applyPreviewTransform(placeholderRect.left, placeholderRect.top);
1046 // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since
1047 // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
1048 // apply its style, we take advantage of the available info to figure out whether we need to
1049 // bind the event in the first place.
1050 const duration = getTransformTransitionDurationInMs(this._preview);
1051 if (duration === 0) {
1052 return Promise.resolve();
1053 }
1054 return this._ngZone.runOutsideAngular(() => {
1055 return new Promise(resolve => {
1056 const handler = ((event) => {
1057 var _a;
1058 if (!event || (_getEventTarget(event) === this._preview &&
1059 event.propertyName === 'transform')) {
1060 (_a = this._preview) === null || _a === void 0 ? void 0 : _a.removeEventListener('transitionend', handler);
1061 resolve();
1062 clearTimeout(timeout);
1063 }
1064 });
1065 // If a transition is short enough, the browser might not fire the `transitionend` event.
1066 // Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
1067 // fire if the transition hasn't completed when it was supposed to.
1068 const timeout = setTimeout(handler, duration * 1.5);
1069 this._preview.addEventListener('transitionend', handler);
1070 });
1071 });
1072 }
1073 /** Creates an element that will be shown instead of the current element while dragging. */
1074 _createPlaceholderElement() {
1075 const placeholderConfig = this._placeholderTemplate;
1076 const placeholderTemplate = placeholderConfig ? placeholderConfig.template : null;
1077 let placeholder;
1078 if (placeholderTemplate) {
1079 this._placeholderRef = placeholderConfig.viewContainer.createEmbeddedView(placeholderTemplate, placeholderConfig.context);
1080 this._placeholderRef.detectChanges();
1081 placeholder = getRootNode(this._placeholderRef, this._document);
1082 }
1083 else {
1084 placeholder = deepCloneNode(this._rootElement);
1085 }
1086 placeholder.classList.add('cdk-drag-placeholder');
1087 return placeholder;
1088 }
1089 /**
1090 * Figures out the coordinates at which an element was picked up.
1091 * @param referenceElement Element that initiated the dragging.
1092 * @param event Event that initiated the dragging.
1093 */
1094 _getPointerPositionInElement(referenceElement, event) {
1095 const elementRect = this._rootElement.getBoundingClientRect();
1096 const handleElement = referenceElement === this._rootElement ? null : referenceElement;
1097 const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect;
1098 const point = isTouchEvent(event) ? event.targetTouches[0] : event;
1099 const scrollPosition = this._getViewportScrollPosition();
1100 const x = point.pageX - referenceRect.left - scrollPosition.left;
1101 const y = point.pageY - referenceRect.top - scrollPosition.top;
1102 return {
1103 x: referenceRect.left - elementRect.left + x,
1104 y: referenceRect.top - elementRect.top + y
1105 };
1106 }
1107 /** Determines the point of the page that was touched by the user. */
1108 _getPointerPositionOnPage(event) {
1109 const scrollPosition = this._getViewportScrollPosition();
1110 const point = isTouchEvent(event) ?
1111 // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
1112 // Also note that on real devices we're guaranteed for either `touches` or `changedTouches`
1113 // to have a value, but Firefox in device emulation mode has a bug where both can be empty
1114 // for `touchstart` and `touchend` so we fall back to a dummy object in order to avoid
1115 // throwing an error. The value returned here will be incorrect, but since this only
1116 // breaks inside a developer tool and the value is only used for secondary information,
1117 // we can get away with it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1615824.
1118 (event.touches[0] || event.changedTouches[0] || { pageX: 0, pageY: 0 }) : event;
1119 const x = point.pageX - scrollPosition.left;
1120 const y = point.pageY - scrollPosition.top;
1121 // if dragging SVG element, try to convert from the screen coordinate system to the SVG
1122 // coordinate system
1123 if (this._ownerSVGElement) {
1124 const svgMatrix = this._ownerSVGElement.getScreenCTM();
1125 if (svgMatrix) {
1126 const svgPoint = this._ownerSVGElement.createSVGPoint();
1127 svgPoint.x = x;
1128 svgPoint.y = y;
1129 return svgPoint.matrixTransform(svgMatrix.inverse());
1130 }
1131 }
1132 return { x, y };
1133 }
1134 /** Gets the pointer position on the page, accounting for any position constraints. */
1135 _getConstrainedPointerPosition(point) {
1136 const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
1137 let { x, y } = this.constrainPosition ? this.constrainPosition(point, this) : point;
1138 if (this.lockAxis === 'x' || dropContainerLock === 'x') {
1139 y = this._pickupPositionOnPage.y;
1140 }
1141 else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
1142 x = this._pickupPositionOnPage.x;
1143 }
1144 if (this._boundaryRect) {
1145 const { x: pickupX, y: pickupY } = this._pickupPositionInElement;
1146 const boundaryRect = this._boundaryRect;
1147 const previewRect = this._previewRect;
1148 const minY = boundaryRect.top + pickupY;
1149 const maxY = boundaryRect.bottom - (previewRect.height - pickupY);
1150 const minX = boundaryRect.left + pickupX;
1151 const maxX = boundaryRect.right - (previewRect.width - pickupX);
1152 x = clamp$1(x, minX, maxX);
1153 y = clamp$1(y, minY, maxY);
1154 }
1155 return { x, y };
1156 }
1157 /** Updates the current drag delta, based on the user's current pointer position on the page. */
1158 _updatePointerDirectionDelta(pointerPositionOnPage) {
1159 const { x, y } = pointerPositionOnPage;
1160 const delta = this._pointerDirectionDelta;
1161 const positionSinceLastChange = this._pointerPositionAtLastDirectionChange;
1162 // Amount of pixels the user has dragged since the last time the direction changed.
1163 const changeX = Math.abs(x - positionSinceLastChange.x);
1164 const changeY = Math.abs(y - positionSinceLastChange.y);
1165 // Because we handle pointer events on a per-pixel basis, we don't want the delta
1166 // to change for every pixel, otherwise anything that depends on it can look erratic.
1167 // To make the delta more consistent, we track how much the user has moved since the last
1168 // delta change and we only update it after it has reached a certain threshold.
1169 if (changeX > this._config.pointerDirectionChangeThreshold) {
1170 delta.x = x > positionSinceLastChange.x ? 1 : -1;
1171 positionSinceLastChange.x = x;
1172 }
1173 if (changeY > this._config.pointerDirectionChangeThreshold) {
1174 delta.y = y > positionSinceLastChange.y ? 1 : -1;
1175 positionSinceLastChange.y = y;
1176 }
1177 return delta;
1178 }
1179 /** Toggles the native drag interactions, based on how many handles are registered. */
1180 _toggleNativeDragInteractions() {
1181 if (!this._rootElement || !this._handles) {
1182 return;
1183 }
1184 const shouldEnable = this._handles.length > 0 || !this.isDragging();
1185 if (shouldEnable !== this._nativeInteractionsEnabled) {
1186 this._nativeInteractionsEnabled = shouldEnable;
1187 toggleNativeDragInteractions(this._rootElement, shouldEnable);
1188 }
1189 }
1190 /** Removes the manually-added event listeners from the root element. */
1191 _removeRootElementListeners(element) {
1192 element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
1193 element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
1194 }
1195 /**
1196 * Applies a `transform` to the root element, taking into account any existing transforms on it.
1197 * @param x New transform value along the X axis.
1198 * @param y New transform value along the Y axis.
1199 */
1200 _applyRootElementTransform(x, y) {
1201 const transform = getTransform(x, y);
1202 // Cache the previous transform amount only after the first drag sequence, because
1203 // we don't want our own transforms to stack on top of each other.
1204 // Should be excluded none because none + translate3d(x, y, x) is invalid css
1205 if (this._initialTransform == null) {
1206 this._initialTransform = this._rootElement.style.transform
1207 && this._rootElement.style.transform != 'none'
1208 ? this._rootElement.style.transform
1209 : '';
1210 }
1211 // Preserve the previous `transform` value, if there was one. Note that we apply our own
1212 // transform before the user's, because things like rotation can affect which direction
1213 // the element will be translated towards.
1214 this._rootElement.style.transform = combineTransforms(transform, this._initialTransform);
1215 }
1216 /**
1217 * Applies a `transform` to the preview, taking into account any existing transforms on it.
1218 * @param x New transform value along the X axis.
1219 * @param y New transform value along the Y axis.
1220 */
1221 _applyPreviewTransform(x, y) {
1222 var _a;
1223 // Only apply the initial transform if the preview is a clone of the original element, otherwise
1224 // it could be completely different and the transform might not make sense anymore.
1225 const initialTransform = ((_a = this._previewTemplate) === null || _a === void 0 ? void 0 : _a.template) ? undefined : this._initialTransform;
1226 const transform = getTransform(x, y);
1227 this._preview.style.transform = combineTransforms(transform, initialTransform);
1228 }
1229 /**
1230 * Gets the distance that the user has dragged during the current drag sequence.
1231 * @param currentPosition Current position of the user's pointer.
1232 */
1233 _getDragDistance(currentPosition) {
1234 const pickupPosition = this._pickupPositionOnPage;
1235 if (pickupPosition) {
1236 return { x: currentPosition.x - pickupPosition.x, y: currentPosition.y - pickupPosition.y };
1237 }
1238 return { x: 0, y: 0 };
1239 }
1240 /** Cleans up any cached element dimensions that we don't need after dragging has stopped. */
1241 _cleanupCachedDimensions() {
1242 this._boundaryRect = this._previewRect = undefined;
1243 this._parentPositions.clear();
1244 }
1245 /**
1246 * Checks whether the element is still inside its boundary after the viewport has been resized.
1247 * If not, the position is adjusted so that the element fits again.
1248 */
1249 _containInsideBoundaryOnResize() {
1250 let { x, y } = this._passiveTransform;
1251 if ((x === 0 && y === 0) || this.isDragging() || !this._boundaryElement) {
1252 return;
1253 }
1254 const boundaryRect = this._boundaryElement.getBoundingClientRect();
1255 const elementRect = this._rootElement.getBoundingClientRect();
1256 // It's possible that the element got hidden away after dragging (e.g. by switching to a
1257 // different tab). Don't do anything in this case so we don't clear the user's position.
1258 if ((boundaryRect.width === 0 && boundaryRect.height === 0) ||
1259 (elementRect.width === 0 && elementRect.height === 0)) {
1260 return;
1261 }
1262 const leftOverflow = boundaryRect.left - elementRect.left;
1263 const rightOverflow = elementRect.right - boundaryRect.right;
1264 const topOverflow = boundaryRect.top - elementRect.top;
1265 const bottomOverflow = elementRect.bottom - boundaryRect.bottom;
1266 // If the element has become wider than the boundary, we can't
1267 // do much to make it fit so we just anchor it to the left.
1268 if (boundaryRect.width > elementRect.width) {
1269 if (leftOverflow > 0) {
1270 x += leftOverflow;
1271 }
1272 if (rightOverflow > 0) {
1273 x -= rightOverflow;
1274 }
1275 }
1276 else {
1277 x = 0;
1278 }
1279 // If the element has become taller than the boundary, we can't
1280 // do much to make it fit so we just anchor it to the top.
1281 if (boundaryRect.height > elementRect.height) {
1282 if (topOverflow > 0) {
1283 y += topOverflow;
1284 }
1285 if (bottomOverflow > 0) {
1286 y -= bottomOverflow;
1287 }
1288 }
1289 else {
1290 y = 0;
1291 }
1292 if (x !== this._passiveTransform.x || y !== this._passiveTransform.y) {
1293 this.setFreeDragPosition({ y, x });
1294 }
1295 }
1296 /** Gets the drag start delay, based on the event type. */
1297 _getDragStartDelay(event) {
1298 const value = this.dragStartDelay;
1299 if (typeof value === 'number') {
1300 return value;
1301 }
1302 else if (isTouchEvent(event)) {
1303 return value.touch;
1304 }
1305 return value ? value.mouse : 0;
1306 }
1307 /** Updates the internal state of the draggable element when scrolling has occurred. */
1308 _updateOnScroll(event) {
1309 const scrollDifference = this._parentPositions.handleScroll(event);
1310 if (scrollDifference) {
1311 const target = _getEventTarget(event);
1312 // ClientRect dimensions are based on the scroll position of the page and its parent node so
1313 // we have to update the cached boundary ClientRect if the user has scrolled. Check for
1314 // the `document` specifically since IE doesn't support `contains` on it.
1315 if (this._boundaryRect && (target === this._document ||
1316 (target !== this._boundaryElement && target.contains(this._boundaryElement)))) {
1317 adjustClientRect(this._boundaryRect, scrollDifference.top, scrollDifference.left);
1318 }
1319 this._pickupPositionOnPage.x += scrollDifference.left;
1320 this._pickupPositionOnPage.y += scrollDifference.top;
1321 // If we're in free drag mode, we have to update the active transform, because
1322 // it isn't relative to the viewport like the preview inside a drop list.
1323 if (!this._dropContainer) {
1324 this._activeTransform.x -= scrollDifference.left;
1325 this._activeTransform.y -= scrollDifference.top;
1326 this._applyRootElementTransform(this._activeTransform.x, this._activeTransform.y);
1327 }
1328 }
1329 }
1330 /** Gets the scroll position of the viewport. */
1331 _getViewportScrollPosition() {
1332 const cachedPosition = this._parentPositions.positions.get(this._document);
1333 return cachedPosition ? cachedPosition.scrollPosition :
1334 this._viewportRuler.getViewportScrollPosition();
1335 }
1336 /**
1337 * Lazily resolves and returns the shadow root of the element. We do this in a function, rather
1338 * than saving it in property directly on init, because we want to resolve it as late as possible
1339 * in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
1340 * constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
1341 */
1342 _getShadowRoot() {
1343 if (this._cachedShadowRoot === undefined) {
1344 this._cachedShadowRoot = _getShadowRoot(this._rootElement);
1345 }
1346 return this._cachedShadowRoot;
1347 }
1348 /** Gets the element into which the drag preview should be inserted. */
1349 _getPreviewInsertionPoint(initialParent, shadowRoot) {
1350 const previewContainer = this._previewContainer || 'global';
1351 if (previewContainer === 'parent') {
1352 return initialParent;
1353 }
1354 if (previewContainer === 'global') {
1355 const documentRef = this._document;
1356 // We can't use the body if the user is in fullscreen mode,
1357 // because the preview will render under the fullscreen element.
1358 // TODO(crisbeto): dedupe this with the `FullscreenOverlayContainer` eventually.
1359 return shadowRoot ||
1360 documentRef.fullscreenElement ||
1361 documentRef.webkitFullscreenElement ||
1362 documentRef.mozFullScreenElement ||
1363 documentRef.msFullscreenElement ||
1364 documentRef.body;
1365 }
1366 return coerceElement(previewContainer);
1367 }
1368}
1369/**
1370 * Gets a 3d `transform` that can be applied to an element.
1371 * @param x Desired position of the element along the X axis.
1372 * @param y Desired position of the element along the Y axis.
1373 */
1374function getTransform(x, y) {
1375 // Round the transforms since some browsers will
1376 // blur the elements for sub-pixel transforms.
1377 return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
1378}
1379/** Clamps a value between a minimum and a maximum. */
1380function clamp$1(value, min, max) {
1381 return Math.max(min, Math.min(max, value));
1382}
1383/**
1384 * Helper to remove a node from the DOM and to do all the necessary null checks.
1385 * @param node Node to be removed.
1386 */
1387function removeNode(node) {
1388 if (node && node.parentNode) {
1389 node.parentNode.removeChild(node);
1390 }
1391}
1392/** Determines whether an event is a touch event. */
1393function isTouchEvent(event) {
1394 // This function is called for every pixel that the user has dragged so we need it to be
1395 // as fast as possible. Since we only bind mouse events and touch events, we can assume
1396 // that if the event's name starts with `t`, it's a touch event.
1397 return event.type[0] === 't';
1398}
1399/**
1400 * Gets the root HTML element of an embedded view.
1401 * If the root is not an HTML element it gets wrapped in one.
1402 */
1403function getRootNode(viewRef, _document) {
1404 const rootNodes = viewRef.rootNodes;
1405 if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {
1406 return rootNodes[0];
1407 }
1408 const wrapper = _document.createElement('div');
1409 rootNodes.forEach(node => wrapper.appendChild(node));
1410 return wrapper;
1411}
1412/**
1413 * Matches the target element's size to the source's size.
1414 * @param target Element that needs to be resized.
1415 * @param sourceRect Dimensions of the source element.
1416 */
1417function matchElementSize(target, sourceRect) {
1418 target.style.width = `${sourceRect.width}px`;
1419 target.style.height = `${sourceRect.height}px`;
1420 target.style.transform = getTransform(sourceRect.left, sourceRect.top);
1421}
1422
1423/**
1424 * @license
1425 * Copyright Google LLC All Rights Reserved.
1426 *
1427 * Use of this source code is governed by an MIT-style license that can be
1428 * found in the LICENSE file at https://angular.io/license
1429 */
1430/**
1431 * Moves an item one index in an array to another.
1432 * @param array Array in which to move the item.
1433 * @param fromIndex Starting index of the item.
1434 * @param toIndex Index to which the item should be moved.
1435 */
1436function moveItemInArray(array, fromIndex, toIndex) {
1437 const from = clamp(fromIndex, array.length - 1);
1438 const to = clamp(toIndex, array.length - 1);
1439 if (from === to) {
1440 return;
1441 }
1442 const target = array[from];
1443 const delta = to < from ? -1 : 1;
1444 for (let i = from; i !== to; i += delta) {
1445 array[i] = array[i + delta];
1446 }
1447 array[to] = target;
1448}
1449/**
1450 * Moves an item from one array to another.
1451 * @param currentArray Array from which to transfer the item.
1452 * @param targetArray Array into which to put the item.
1453 * @param currentIndex Index of the item in its current array.
1454 * @param targetIndex Index at which to insert the item.
1455 */
1456function transferArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
1457 const from = clamp(currentIndex, currentArray.length - 1);
1458 const to = clamp(targetIndex, targetArray.length);
1459 if (currentArray.length) {
1460 targetArray.splice(to, 0, currentArray.splice(from, 1)[0]);
1461 }
1462}
1463/**
1464 * Copies an item from one array to another, leaving it in its
1465 * original position in current array.
1466 * @param currentArray Array from which to copy the item.
1467 * @param targetArray Array into which is copy the item.
1468 * @param currentIndex Index of the item in its current array.
1469 * @param targetIndex Index at which to insert the item.
1470 *
1471 */
1472function copyArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
1473 const to = clamp(targetIndex, targetArray.length);
1474 if (currentArray.length) {
1475 targetArray.splice(to, 0, currentArray[currentIndex]);
1476 }
1477}
1478/** Clamps a number between zero and a maximum. */
1479function clamp(value, max) {
1480 return Math.max(0, Math.min(max, value));
1481}
1482
1483/**
1484 * @license
1485 * Copyright Google LLC All Rights Reserved.
1486 *
1487 * Use of this source code is governed by an MIT-style license that can be
1488 * found in the LICENSE file at https://angular.io/license
1489 */
1490/**
1491 * Proximity, as a ratio to width/height, at which a
1492 * dragged item will affect the drop container.
1493 */
1494const DROP_PROXIMITY_THRESHOLD = 0.05;
1495/**
1496 * Proximity, as a ratio to width/height at which to start auto-scrolling the drop list or the
1497 * viewport. The value comes from trying it out manually until it feels right.
1498 */
1499const SCROLL_PROXIMITY_THRESHOLD = 0.05;
1500/**
1501 * Reference to a drop list. Used to manipulate or dispose of the container.
1502 */
1503class DropListRef {
1504 constructor(element, _dragDropRegistry, _document, _ngZone, _viewportRuler) {
1505 this._dragDropRegistry = _dragDropRegistry;
1506 this._ngZone = _ngZone;
1507 this._viewportRuler = _viewportRuler;
1508 /** Whether starting a dragging sequence from this container is disabled. */
1509 this.disabled = false;
1510 /** Whether sorting items within the list is disabled. */
1511 this.sortingDisabled = false;
1512 /**
1513 * Whether auto-scrolling the view when the user
1514 * moves their pointer close to the edges is disabled.
1515 */
1516 this.autoScrollDisabled = false;
1517 /** Number of pixels to scroll for each frame when auto-scrolling an element. */
1518 this.autoScrollStep = 2;
1519 /**
1520 * Function that is used to determine whether an item
1521 * is allowed to be moved into a drop container.
1522 */
1523 this.enterPredicate = () => true;
1524 /** Functions that is used to determine whether an item can be sorted into a particular index. */
1525 this.sortPredicate = () => true;
1526 /** Emits right before dragging has started. */
1527 this.beforeStarted = new Subject();
1528 /**
1529 * Emits when the user has moved a new drag item into this container.
1530 */
1531 this.entered = new Subject();
1532 /**
1533 * Emits when the user removes an item from the container
1534 * by dragging it into another container.
1535 */
1536 this.exited = new Subject();
1537 /** Emits when the user drops an item inside the container. */
1538 this.dropped = new Subject();
1539 /** Emits as the user is swapping items while actively dragging. */
1540 this.sorted = new Subject();
1541 /** Whether an item in the list is being dragged. */
1542 this._isDragging = false;
1543 /** Cache of the dimensions of all the items inside the container. */
1544 this._itemPositions = [];
1545 /**
1546 * Keeps track of the item that was last swapped with the dragged item, as well as what direction
1547 * the pointer was moving in when the swap occured and whether the user's pointer continued to
1548 * overlap with the swapped item after the swapping occurred.
1549 */
1550 this._previousSwap = { drag: null, delta: 0, overlaps: false };
1551 /** Draggable items in the container. */
1552 this._draggables = [];
1553 /** Drop lists that are connected to the current one. */
1554 this._siblings = [];
1555 /** Direction in which the list is oriented. */
1556 this._orientation = 'vertical';
1557 /** Connected siblings that currently have a dragged item. */
1558 this._activeSiblings = new Set();
1559 /** Layout direction of the drop list. */
1560 this._direction = 'ltr';
1561 /** Subscription to the window being scrolled. */
1562 this._viewportScrollSubscription = Subscription.EMPTY;
1563 /** Vertical direction in which the list is currently scrolling. */
1564 this._verticalScrollDirection = 0 /* NONE */;
1565 /** Horizontal direction in which the list is currently scrolling. */
1566 this._horizontalScrollDirection = 0 /* NONE */;
1567 /** Used to signal to the current auto-scroll sequence when to stop. */
1568 this._stopScrollTimers = new Subject();
1569 /** Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly. */
1570 this._cachedShadowRoot = null;
1571 /** Starts the interval that'll auto-scroll the element. */
1572 this._startScrollInterval = () => {
1573 this._stopScrolling();
1574 interval(0, animationFrameScheduler)
1575 .pipe(takeUntil(this._stopScrollTimers))
1576 .subscribe(() => {
1577 const node = this._scrollNode;
1578 const scrollStep = this.autoScrollStep;
1579 if (this._verticalScrollDirection === 1 /* UP */) {
1580 incrementVerticalScroll(node, -scrollStep);
1581 }
1582 else if (this._verticalScrollDirection === 2 /* DOWN */) {
1583 incrementVerticalScroll(node, scrollStep);
1584 }
1585 if (this._horizontalScrollDirection === 1 /* LEFT */) {
1586 incrementHorizontalScroll(node, -scrollStep);
1587 }
1588 else if (this._horizontalScrollDirection === 2 /* RIGHT */) {
1589 incrementHorizontalScroll(node, scrollStep);
1590 }
1591 });
1592 };
1593 this.element = coerceElement(element);
1594 this._document = _document;
1595 this.withScrollableParents([this.element]);
1596 _dragDropRegistry.registerDropContainer(this);
1597 this._parentPositions = new ParentPositionTracker(_document, _viewportRuler);
1598 }
1599 /** Removes the drop list functionality from the DOM element. */
1600 dispose() {
1601 this._stopScrolling();
1602 this._stopScrollTimers.complete();
1603 this._viewportScrollSubscription.unsubscribe();
1604 this.beforeStarted.complete();
1605 this.entered.complete();
1606 this.exited.complete();
1607 this.dropped.complete();
1608 this.sorted.complete();
1609 this._activeSiblings.clear();
1610 this._scrollNode = null;
1611 this._parentPositions.clear();
1612 this._dragDropRegistry.removeDropContainer(this);
1613 }
1614 /** Whether an item from this list is currently being dragged. */
1615 isDragging() {
1616 return this._isDragging;
1617 }
1618 /** Starts dragging an item. */
1619 start() {
1620 this._draggingStarted();
1621 this._notifyReceivingSiblings();
1622 }
1623 /**
1624 * Emits an event to indicate that the user moved an item into the container.
1625 * @param item Item that was moved into the container.
1626 * @param pointerX Position of the item along the X axis.
1627 * @param pointerY Position of the item along the Y axis.
1628 * @param index Index at which the item entered. If omitted, the container will try to figure it
1629 * out automatically.
1630 */
1631 enter(item, pointerX, pointerY, index) {
1632 this._draggingStarted();
1633 // If sorting is disabled, we want the item to return to its starting
1634 // position if the user is returning it to its initial container.
1635 let newIndex;
1636 if (index == null) {
1637 newIndex = this.sortingDisabled ? this._draggables.indexOf(item) : -1;
1638 if (newIndex === -1) {
1639 // We use the coordinates of where the item entered the drop
1640 // zone to figure out at which index it should be inserted.
1641 newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
1642 }
1643 }
1644 else {
1645 newIndex = index;
1646 }
1647 const activeDraggables = this._activeDraggables;
1648 const currentIndex = activeDraggables.indexOf(item);
1649 const placeholder = item.getPlaceholderElement();
1650 let newPositionReference = activeDraggables[newIndex];
1651 // If the item at the new position is the same as the item that is being dragged,
1652 // it means that we're trying to restore the item to its initial position. In this
1653 // case we should use the next item from the list as the reference.
1654 if (newPositionReference === item) {
1655 newPositionReference = activeDraggables[newIndex + 1];
1656 }
1657 // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
1658 // into another container and back again), we have to ensure that it isn't duplicated.
1659 if (currentIndex > -1) {
1660 activeDraggables.splice(currentIndex, 1);
1661 }
1662 // Don't use items that are being dragged as a reference, because
1663 // their element has been moved down to the bottom of the body.
1664 if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
1665 const element = newPositionReference.getRootElement();
1666 element.parentElement.insertBefore(placeholder, element);
1667 activeDraggables.splice(newIndex, 0, item);
1668 }
1669 else if (this._shouldEnterAsFirstChild(pointerX, pointerY)) {
1670 const reference = activeDraggables[0].getRootElement();
1671 reference.parentNode.insertBefore(placeholder, reference);
1672 activeDraggables.unshift(item);
1673 }
1674 else {
1675 coerceElement(this.element).appendChild(placeholder);
1676 activeDraggables.push(item);
1677 }
1678 // The transform needs to be cleared so it doesn't throw off the measurements.
1679 placeholder.style.transform = '';
1680 // Note that the positions were already cached when we called `start` above,
1681 // but we need to refresh them since the amount of items has changed and also parent rects.
1682 this._cacheItemPositions();
1683 this._cacheParentPositions();
1684 // Notify siblings at the end so that the item has been inserted into the `activeDraggables`.
1685 this._notifyReceivingSiblings();
1686 this.entered.next({ item, container: this, currentIndex: this.getItemIndex(item) });
1687 }
1688 /**
1689 * Removes an item from the container after it was dragged into another container by the user.
1690 * @param item Item that was dragged out.
1691 */
1692 exit(item) {
1693 this._reset();
1694 this.exited.next({ item, container: this });
1695 }
1696 /**
1697 * Drops an item into this container.
1698 * @param item Item being dropped into the container.
1699 * @param currentIndex Index at which the item should be inserted.
1700 * @param previousIndex Index of the item when dragging started.
1701 * @param previousContainer Container from which the item got dragged in.
1702 * @param isPointerOverContainer Whether the user's pointer was over the
1703 * container when the item was dropped.
1704 * @param distance Distance the user has dragged since the start of the dragging sequence.
1705 */
1706 drop(item, currentIndex, previousIndex, previousContainer, isPointerOverContainer, distance, dropPoint) {
1707 this._reset();
1708 this.dropped.next({
1709 item,
1710 currentIndex,
1711 previousIndex,
1712 container: this,
1713 previousContainer,
1714 isPointerOverContainer,
1715 distance,
1716 dropPoint
1717 });
1718 }
1719 /**
1720 * Sets the draggable items that are a part of this list.
1721 * @param items Items that are a part of this list.
1722 */
1723 withItems(items) {
1724 const previousItems = this._draggables;
1725 this._draggables = items;
1726 items.forEach(item => item._withDropContainer(this));
1727 if (this.isDragging()) {
1728 const draggedItems = previousItems.filter(item => item.isDragging());
1729 // If all of the items being dragged were removed
1730 // from the list, abort the current drag sequence.
1731 if (draggedItems.every(item => items.indexOf(item) === -1)) {
1732 this._reset();
1733 }
1734 else {
1735 this._cacheItems();
1736 }
1737 }
1738 return this;
1739 }
1740 /** Sets the layout direction of the drop list. */
1741 withDirection(direction) {
1742 this._direction = direction;
1743 return this;
1744 }
1745 /**
1746 * Sets the containers that are connected to this one. When two or more containers are
1747 * connected, the user will be allowed to transfer items between them.
1748 * @param connectedTo Other containers that the current containers should be connected to.
1749 */
1750 connectedTo(connectedTo) {
1751 this._siblings = connectedTo.slice();
1752 return this;
1753 }
1754 /**
1755 * Sets the orientation of the container.
1756 * @param orientation New orientation for the container.
1757 */
1758 withOrientation(orientation) {
1759 this._orientation = orientation;
1760 return this;
1761 }
1762 /**
1763 * Sets which parent elements are can be scrolled while the user is dragging.
1764 * @param elements Elements that can be scrolled.
1765 */
1766 withScrollableParents(elements) {
1767 const element = coerceElement(this.element);
1768 // We always allow the current element to be scrollable
1769 // so we need to ensure that it's in the array.
1770 this._scrollableElements =
1771 elements.indexOf(element) === -1 ? [element, ...elements] : elements.slice();
1772 return this;
1773 }
1774 /** Gets the scrollable parents that are registered with this drop container. */
1775 getScrollableParents() {
1776 return this._scrollableElements;
1777 }
1778 /**
1779 * Figures out the index of an item in the container.
1780 * @param item Item whose index should be determined.
1781 */
1782 getItemIndex(item) {
1783 if (!this._isDragging) {
1784 return this._draggables.indexOf(item);
1785 }
1786 // Items are sorted always by top/left in the cache, however they flow differently in RTL.
1787 // The rest of the logic still stands no matter what orientation we're in, however
1788 // we need to invert the array when determining the index.
1789 const items = this._orientation === 'horizontal' && this._direction === 'rtl' ?
1790 this._itemPositions.slice().reverse() : this._itemPositions;
1791 return findIndex(items, currentItem => currentItem.drag === item);
1792 }
1793 /**
1794 * Whether the list is able to receive the item that
1795 * is currently being dragged inside a connected drop list.
1796 */
1797 isReceiving() {
1798 return this._activeSiblings.size > 0;
1799 }
1800 /**
1801 * Sorts an item inside the container based on its position.
1802 * @param item Item to be sorted.
1803 * @param pointerX Position of the item along the X axis.
1804 * @param pointerY Position of the item along the Y axis.
1805 * @param pointerDelta Direction in which the pointer is moving along each axis.
1806 */
1807 _sortItem(item, pointerX, pointerY, pointerDelta) {
1808 // Don't sort the item if sorting is disabled or it's out of range.
1809 if (this.sortingDisabled || !this._clientRect ||
1810 !isPointerNearClientRect(this._clientRect, DROP_PROXIMITY_THRESHOLD, pointerX, pointerY)) {
1811 return;
1812 }
1813 const siblings = this._itemPositions;
1814 const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
1815 if (newIndex === -1 && siblings.length > 0) {
1816 return;
1817 }
1818 const isHorizontal = this._orientation === 'horizontal';
1819 const currentIndex = findIndex(siblings, currentItem => currentItem.drag === item);
1820 const siblingAtNewPosition = siblings[newIndex];
1821 const currentPosition = siblings[currentIndex].clientRect;
1822 const newPosition = siblingAtNewPosition.clientRect;
1823 const delta = currentIndex > newIndex ? 1 : -1;
1824 // How many pixels the item's placeholder should be offset.
1825 const itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta);
1826 // How many pixels all the other items should be offset.
1827 const siblingOffset = this._getSiblingOffsetPx(currentIndex, siblings, delta);
1828 // Save the previous order of the items before moving the item to its new index.
1829 // We use this to check whether an item has been moved as a result of the sorting.
1830 const oldOrder = siblings.slice();
1831 // Shuffle the array in place.
1832 moveItemInArray(siblings, currentIndex, newIndex);
1833 this.sorted.next({
1834 previousIndex: currentIndex,
1835 currentIndex: newIndex,
1836 container: this,
1837 item
1838 });
1839 siblings.forEach((sibling, index) => {
1840 // Don't do anything if the position hasn't changed.
1841 if (oldOrder[index] === sibling) {
1842 return;
1843 }
1844 const isDraggedItem = sibling.drag === item;
1845 const offset = isDraggedItem ? itemOffset : siblingOffset;
1846 const elementToOffset = isDraggedItem ? item.getPlaceholderElement() :
1847 sibling.drag.getRootElement();
1848 // Update the offset to reflect the new position.
1849 sibling.offset += offset;
1850 // Since we're moving the items with a `transform`, we need to adjust their cached
1851 // client rects to reflect their new position, as well as swap their positions in the cache.
1852 // Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the
1853 // elements may be mid-animation which will give us a wrong result.
1854 if (isHorizontal) {
1855 // Round the transforms since some browsers will
1856 // blur the elements, for sub-pixel transforms.
1857 elementToOffset.style.transform = combineTransforms(`translate3d(${Math.round(sibling.offset)}px, 0, 0)`, sibling.initialTransform);
1858 adjustClientRect(sibling.clientRect, 0, offset);
1859 }
1860 else {
1861 elementToOffset.style.transform = combineTransforms(`translate3d(0, ${Math.round(sibling.offset)}px, 0)`, sibling.initialTransform);
1862 adjustClientRect(sibling.clientRect, offset, 0);
1863 }
1864 });
1865 // Note that it's important that we do this after the client rects have been adjusted.
1866 this._previousSwap.overlaps = isInsideClientRect(newPosition, pointerX, pointerY);
1867 this._previousSwap.drag = siblingAtNewPosition.drag;
1868 this._previousSwap.delta = isHorizontal ? pointerDelta.x : pointerDelta.y;
1869 }
1870 /**
1871 * Checks whether the user's pointer is close to the edges of either the
1872 * viewport or the drop list and starts the auto-scroll sequence.
1873 * @param pointerX User's pointer position along the x axis.
1874 * @param pointerY User's pointer position along the y axis.
1875 */
1876 _startScrollingIfNecessary(pointerX, pointerY) {
1877 if (this.autoScrollDisabled) {
1878 return;
1879 }
1880 let scrollNode;
1881 let verticalScrollDirection = 0 /* NONE */;
1882 let horizontalScrollDirection = 0 /* NONE */;
1883 // Check whether we should start scrolling any of the parent containers.
1884 this._parentPositions.positions.forEach((position, element) => {
1885 // We have special handling for the `document` below. Also this would be
1886 // nicer with a for...of loop, but it requires changing a compiler flag.
1887 if (element === this._document || !position.clientRect || scrollNode) {
1888 return;
1889 }
1890 if (isPointerNearClientRect(position.clientRect, DROP_PROXIMITY_THRESHOLD, pointerX, pointerY)) {
1891 [verticalScrollDirection, horizontalScrollDirection] = getElementScrollDirections(element, position.clientRect, pointerX, pointerY);
1892 if (verticalScrollDirection || horizontalScrollDirection) {
1893 scrollNode = element;
1894 }
1895 }
1896 });
1897 // Otherwise check if we can start scrolling the viewport.
1898 if (!verticalScrollDirection && !horizontalScrollDirection) {
1899 const { width, height } = this._viewportRuler.getViewportSize();
1900 const clientRect = { width, height, top: 0, right: width, bottom: height, left: 0 };
1901 verticalScrollDirection = getVerticalScrollDirection(clientRect, pointerY);
1902 horizontalScrollDirection = getHorizontalScrollDirection(clientRect, pointerX);
1903 scrollNode = window;
1904 }
1905 if (scrollNode && (verticalScrollDirection !== this._verticalScrollDirection ||
1906 horizontalScrollDirection !== this._horizontalScrollDirection ||
1907 scrollNode !== this._scrollNode)) {
1908 this._verticalScrollDirection = verticalScrollDirection;
1909 this._horizontalScrollDirection = horizontalScrollDirection;
1910 this._scrollNode = scrollNode;
1911 if ((verticalScrollDirection || horizontalScrollDirection) && scrollNode) {
1912 this._ngZone.runOutsideAngular(this._startScrollInterval);
1913 }
1914 else {
1915 this._stopScrolling();
1916 }
1917 }
1918 }
1919 /** Stops any currently-running auto-scroll sequences. */
1920 _stopScrolling() {
1921 this._stopScrollTimers.next();
1922 }
1923 /** Starts the dragging sequence within the list. */
1924 _draggingStarted() {
1925 const styles = coerceElement(this.element).style;
1926 this.beforeStarted.next();
1927 this._isDragging = true;
1928 // We need to disable scroll snapping while the user is dragging, because it breaks automatic
1929 // scrolling. The browser seems to round the value based on the snapping points which means
1930 // that we can't increment/decrement the scroll position.
1931 this._initialScrollSnap = styles.msScrollSnapType || styles.scrollSnapType || '';
1932 styles.scrollSnapType = styles.msScrollSnapType = 'none';
1933 this._cacheItems();
1934 this._viewportScrollSubscription.unsubscribe();
1935 this._listenToScrollEvents();
1936 }
1937 /** Caches the positions of the configured scrollable parents. */
1938 _cacheParentPositions() {
1939 const element = coerceElement(this.element);
1940 this._parentPositions.cache(this._scrollableElements);
1941 // The list element is always in the `scrollableElements`
1942 // so we can take advantage of the cached `ClientRect`.
1943 this._clientRect = this._parentPositions.positions.get(element).clientRect;
1944 }
1945 /** Refreshes the position cache of the items and sibling containers. */
1946 _cacheItemPositions() {
1947 const isHorizontal = this._orientation === 'horizontal';
1948 this._itemPositions = this._activeDraggables.map(drag => {
1949 const elementToMeasure = drag.getVisibleElement();
1950 return {
1951 drag,
1952 offset: 0,
1953 initialTransform: elementToMeasure.style.transform || '',
1954 clientRect: getMutableClientRect(elementToMeasure),
1955 };
1956 }).sort((a, b) => {
1957 return isHorizontal ? a.clientRect.left - b.clientRect.left :
1958 a.clientRect.top - b.clientRect.top;
1959 });
1960 }
1961 /** Resets the container to its initial state. */
1962 _reset() {
1963 this._isDragging = false;
1964 const styles = coerceElement(this.element).style;
1965 styles.scrollSnapType = styles.msScrollSnapType = this._initialScrollSnap;
1966 // TODO(crisbeto): may have to wait for the animations to finish.
1967 this._activeDraggables.forEach(item => {
1968 var _a;
1969 const rootElement = item.getRootElement();
1970 if (rootElement) {
1971 const initialTransform = (_a = this._itemPositions
1972 .find(current => current.drag === item)) === null || _a === void 0 ? void 0 : _a.initialTransform;
1973 rootElement.style.transform = initialTransform || '';
1974 }
1975 });
1976 this._siblings.forEach(sibling => sibling._stopReceiving(this));
1977 this._activeDraggables = [];
1978 this._itemPositions = [];
1979 this._previousSwap.drag = null;
1980 this._previousSwap.delta = 0;
1981 this._previousSwap.overlaps = false;
1982 this._stopScrolling();
1983 this._viewportScrollSubscription.unsubscribe();
1984 this._parentPositions.clear();
1985 }
1986 /**
1987 * Gets the offset in pixels by which the items that aren't being dragged should be moved.
1988 * @param currentIndex Index of the item currently being dragged.
1989 * @param siblings All of the items in the list.
1990 * @param delta Direction in which the user is moving.
1991 */
1992 _getSiblingOffsetPx(currentIndex, siblings, delta) {
1993 const isHorizontal = this._orientation === 'horizontal';
1994 const currentPosition = siblings[currentIndex].clientRect;
1995 const immediateSibling = siblings[currentIndex + delta * -1];
1996 let siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta;
1997 if (immediateSibling) {
1998 const start = isHorizontal ? 'left' : 'top';
1999 const end = isHorizontal ? 'right' : 'bottom';
2000 // Get the spacing between the start of the current item and the end of the one immediately
2001 // after it in the direction in which the user is dragging, or vice versa. We add it to the
2002 // offset in order to push the element to where it will be when it's inline and is influenced
2003 // by the `margin` of its siblings.
2004 if (delta === -1) {
2005 siblingOffset -= immediateSibling.clientRect[start] - currentPosition[end];
2006 }
2007 else {
2008 siblingOffset += currentPosition[start] - immediateSibling.clientRect[end];
2009 }
2010 }
2011 return siblingOffset;
2012 }
2013 /**
2014 * Gets the offset in pixels by which the item that is being dragged should be moved.
2015 * @param currentPosition Current position of the item.
2016 * @param newPosition Position of the item where the current item should be moved.
2017 * @param delta Direction in which the user is moving.
2018 */
2019 _getItemOffsetPx(currentPosition, newPosition, delta) {
2020 const isHorizontal = this._orientation === 'horizontal';
2021 let itemOffset = isHorizontal ? newPosition.left - currentPosition.left :
2022 newPosition.top - currentPosition.top;
2023 // Account for differences in the item width/height.
2024 if (delta === -1) {
2025 itemOffset += isHorizontal ? newPosition.width - currentPosition.width :
2026 newPosition.height - currentPosition.height;
2027 }
2028 return itemOffset;
2029 }
2030 /**
2031 * Checks if pointer is entering in the first position
2032 * @param pointerX Position of the user's pointer along the X axis.
2033 * @param pointerY Position of the user's pointer along the Y axis.
2034 */
2035 _shouldEnterAsFirstChild(pointerX, pointerY) {
2036 if (!this._activeDraggables.length) {
2037 return false;
2038 }
2039 const itemPositions = this._itemPositions;
2040 const isHorizontal = this._orientation === 'horizontal';
2041 // `itemPositions` are sorted by position while `activeDraggables` are sorted by child index
2042 // check if container is using some sort of "reverse" ordering (eg: flex-direction: row-reverse)
2043 const reversed = itemPositions[0].drag !== this._activeDraggables[0];
2044 if (reversed) {
2045 const lastItemRect = itemPositions[itemPositions.length - 1].clientRect;
2046 return isHorizontal ? pointerX >= lastItemRect.right : pointerY >= lastItemRect.bottom;
2047 }
2048 else {
2049 const firstItemRect = itemPositions[0].clientRect;
2050 return isHorizontal ? pointerX <= firstItemRect.left : pointerY <= firstItemRect.top;
2051 }
2052 }
2053 /**
2054 * Gets the index of an item in the drop container, based on the position of the user's pointer.
2055 * @param item Item that is being sorted.
2056 * @param pointerX Position of the user's pointer along the X axis.
2057 * @param pointerY Position of the user's pointer along the Y axis.
2058 * @param delta Direction in which the user is moving their pointer.
2059 */
2060 _getItemIndexFromPointerPosition(item, pointerX, pointerY, delta) {
2061 const isHorizontal = this._orientation === 'horizontal';
2062 const index = findIndex(this._itemPositions, ({ drag, clientRect }, _, array) => {
2063 if (drag === item) {
2064 // If there's only one item left in the container, it must be
2065 // the dragged item itself so we use it as a reference.
2066 return array.length < 2;
2067 }
2068 if (delta) {
2069 const direction = isHorizontal ? delta.x : delta.y;
2070 // If the user is still hovering over the same item as last time, their cursor hasn't left
2071 // the item after we made the swap, and they didn't change the direction in which they're
2072 // dragging, we don't consider it a direction swap.
2073 if (drag === this._previousSwap.drag && this._previousSwap.overlaps &&
2074 direction === this._previousSwap.delta) {
2075 return false;
2076 }
2077 }
2078 return isHorizontal ?
2079 // Round these down since most browsers report client rects with
2080 // sub-pixel precision, whereas the pointer coordinates are rounded to pixels.
2081 pointerX >= Math.floor(clientRect.left) && pointerX < Math.floor(clientRect.right) :
2082 pointerY >= Math.floor(clientRect.top) && pointerY < Math.floor(clientRect.bottom);
2083 });
2084 return (index === -1 || !this.sortPredicate(index, item, this)) ? -1 : index;
2085 }
2086 /** Caches the current items in the list and their positions. */
2087 _cacheItems() {
2088 this._activeDraggables = this._draggables.slice();
2089 this._cacheItemPositions();
2090 this._cacheParentPositions();
2091 }
2092 /**
2093 * Checks whether the user's pointer is positioned over the container.
2094 * @param x Pointer position along the X axis.
2095 * @param y Pointer position along the Y axis.
2096 */
2097 _isOverContainer(x, y) {
2098 return this._clientRect != null && isInsideClientRect(this._clientRect, x, y);
2099 }
2100 /**
2101 * Figures out whether an item should be moved into a sibling
2102 * drop container, based on its current position.
2103 * @param item Drag item that is being moved.
2104 * @param x Position of the item along the X axis.
2105 * @param y Position of the item along the Y axis.
2106 */
2107 _getSiblingContainerFromPosition(item, x, y) {
2108 return this._siblings.find(sibling => sibling._canReceive(item, x, y));
2109 }
2110 /**
2111 * Checks whether the drop list can receive the passed-in item.
2112 * @param item Item that is being dragged into the list.
2113 * @param x Position of the item along the X axis.
2114 * @param y Position of the item along the Y axis.
2115 */
2116 _canReceive(item, x, y) {
2117 if (!this._clientRect || !isInsideClientRect(this._clientRect, x, y) ||
2118 !this.enterPredicate(item, this)) {
2119 return false;
2120 }
2121 const elementFromPoint = this._getShadowRoot().elementFromPoint(x, y);
2122 // If there's no element at the pointer position, then
2123 // the client rect is probably scrolled out of the view.
2124 if (!elementFromPoint) {
2125 return false;
2126 }
2127 const nativeElement = coerceElement(this.element);
2128 // The `ClientRect`, that we're using to find the container over which the user is
2129 // hovering, doesn't give us any information on whether the element has been scrolled
2130 // out of the view or whether it's overlapping with other containers. This means that
2131 // we could end up transferring the item into a container that's invisible or is positioned
2132 // below another one. We use the result from `elementFromPoint` to get the top-most element
2133 // at the pointer position and to find whether it's one of the intersecting drop containers.
2134 return elementFromPoint === nativeElement || nativeElement.contains(elementFromPoint);
2135 }
2136 /**
2137 * Called by one of the connected drop lists when a dragging sequence has started.
2138 * @param sibling Sibling in which dragging has started.
2139 */
2140 _startReceiving(sibling, items) {
2141 const activeSiblings = this._activeSiblings;
2142 if (!activeSiblings.has(sibling) && items.every(item => {
2143 // Note that we have to add an exception to the `enterPredicate` for items that started off
2144 // in this drop list. The drag ref has logic that allows an item to return to its initial
2145 // container, if it has left the initial container and none of the connected containers
2146 // allow it to enter. See `DragRef._updateActiveDropContainer` for more context.
2147 return this.enterPredicate(item, this) || this._draggables.indexOf(item) > -1;
2148 })) {
2149 activeSiblings.add(sibling);
2150 this._cacheParentPositions();
2151 this._listenToScrollEvents();
2152 }
2153 }
2154 /**
2155 * Called by a connected drop list when dragging has stopped.
2156 * @param sibling Sibling whose dragging has stopped.
2157 */
2158 _stopReceiving(sibling) {
2159 this._activeSiblings.delete(sibling);
2160 this._viewportScrollSubscription.unsubscribe();
2161 }
2162 /**
2163 * Starts listening to scroll events on the viewport.
2164 * Used for updating the internal state of the list.
2165 */
2166 _listenToScrollEvents() {
2167 this._viewportScrollSubscription = this._dragDropRegistry
2168 .scrolled(this._getShadowRoot())
2169 .subscribe(event => {
2170 if (this.isDragging()) {
2171 const scrollDifference = this._parentPositions.handleScroll(event);
2172 if (scrollDifference) {
2173 // Since we know the amount that the user has scrolled we can shift all of the
2174 // client rectangles ourselves. This is cheaper than re-measuring everything and
2175 // we can avoid inconsistent behavior where we might be measuring the element before
2176 // its position has changed.
2177 this._itemPositions.forEach(({ clientRect }) => {
2178 adjustClientRect(clientRect, scrollDifference.top, scrollDifference.left);
2179 });
2180 // We need two loops for this, because we want all of the cached
2181 // positions to be up-to-date before we re-sort the item.
2182 this._itemPositions.forEach(({ drag }) => {
2183 if (this._dragDropRegistry.isDragging(drag)) {
2184 // We need to re-sort the item manually, because the pointer move
2185 // events won't be dispatched while the user is scrolling.
2186 drag._sortFromLastPointerPosition();
2187 }
2188 });
2189 }
2190 }
2191 else if (this.isReceiving()) {
2192 this._cacheParentPositions();
2193 }
2194 });
2195 }
2196 /**
2197 * Lazily resolves and returns the shadow root of the element. We do this in a function, rather
2198 * than saving it in property directly on init, because we want to resolve it as late as possible
2199 * in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
2200 * constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
2201 */
2202 _getShadowRoot() {
2203 if (!this._cachedShadowRoot) {
2204 const shadowRoot = _getShadowRoot(coerceElement(this.element));
2205 this._cachedShadowRoot = shadowRoot || this._document;
2206 }
2207 return this._cachedShadowRoot;
2208 }
2209 /** Notifies any siblings that may potentially receive the item. */
2210 _notifyReceivingSiblings() {
2211 const draggedItems = this._activeDraggables.filter(item => item.isDragging());
2212 this._siblings.forEach(sibling => sibling._startReceiving(this, draggedItems));
2213 }
2214}
2215/**
2216 * Finds the index of an item that matches a predicate function. Used as an equivalent
2217 * of `Array.prototype.findIndex` which isn't part of the standard Google typings.
2218 * @param array Array in which to look for matches.
2219 * @param predicate Function used to determine whether an item is a match.
2220 */
2221function findIndex(array, predicate) {
2222 for (let i = 0; i < array.length; i++) {
2223 if (predicate(array[i], i, array)) {
2224 return i;
2225 }
2226 }
2227 return -1;
2228}
2229/**
2230 * Increments the vertical scroll position of a node.
2231 * @param node Node whose scroll position should change.
2232 * @param amount Amount of pixels that the `node` should be scrolled.
2233 */
2234function incrementVerticalScroll(node, amount) {
2235 if (node === window) {
2236 node.scrollBy(0, amount);
2237 }
2238 else {
2239 // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
2240 node.scrollTop += amount;
2241 }
2242}
2243/**
2244 * Increments the horizontal scroll position of a node.
2245 * @param node Node whose scroll position should change.
2246 * @param amount Amount of pixels that the `node` should be scrolled.
2247 */
2248function incrementHorizontalScroll(node, amount) {
2249 if (node === window) {
2250 node.scrollBy(amount, 0);
2251 }
2252 else {
2253 // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
2254 node.scrollLeft += amount;
2255 }
2256}
2257/**
2258 * Gets whether the vertical auto-scroll direction of a node.
2259 * @param clientRect Dimensions of the node.
2260 * @param pointerY Position of the user's pointer along the y axis.
2261 */
2262function getVerticalScrollDirection(clientRect, pointerY) {
2263 const { top, bottom, height } = clientRect;
2264 const yThreshold = height * SCROLL_PROXIMITY_THRESHOLD;
2265 if (pointerY >= top - yThreshold && pointerY <= top + yThreshold) {
2266 return 1 /* UP */;
2267 }
2268 else if (pointerY >= bottom - yThreshold && pointerY <= bottom + yThreshold) {
2269 return 2 /* DOWN */;
2270 }
2271 return 0 /* NONE */;
2272}
2273/**
2274 * Gets whether the horizontal auto-scroll direction of a node.
2275 * @param clientRect Dimensions of the node.
2276 * @param pointerX Position of the user's pointer along the x axis.
2277 */
2278function getHorizontalScrollDirection(clientRect, pointerX) {
2279 const { left, right, width } = clientRect;
2280 const xThreshold = width * SCROLL_PROXIMITY_THRESHOLD;
2281 if (pointerX >= left - xThreshold && pointerX <= left + xThreshold) {
2282 return 1 /* LEFT */;
2283 }
2284 else if (pointerX >= right - xThreshold && pointerX <= right + xThreshold) {
2285 return 2 /* RIGHT */;
2286 }
2287 return 0 /* NONE */;
2288}
2289/**
2290 * Gets the directions in which an element node should be scrolled,
2291 * assuming that the user's pointer is already within it scrollable region.
2292 * @param element Element for which we should calculate the scroll direction.
2293 * @param clientRect Bounding client rectangle of the element.
2294 * @param pointerX Position of the user's pointer along the x axis.
2295 * @param pointerY Position of the user's pointer along the y axis.
2296 */
2297function getElementScrollDirections(element, clientRect, pointerX, pointerY) {
2298 const computedVertical = getVerticalScrollDirection(clientRect, pointerY);
2299 const computedHorizontal = getHorizontalScrollDirection(clientRect, pointerX);
2300 let verticalScrollDirection = 0 /* NONE */;
2301 let horizontalScrollDirection = 0 /* NONE */;
2302 // Note that we here we do some extra checks for whether the element is actually scrollable in
2303 // a certain direction and we only assign the scroll direction if it is. We do this so that we
2304 // can allow other elements to be scrolled, if the current element can't be scrolled anymore.
2305 // This allows us to handle cases where the scroll regions of two scrollable elements overlap.
2306 if (computedVertical) {
2307 const scrollTop = element.scrollTop;
2308 if (computedVertical === 1 /* UP */) {
2309 if (scrollTop > 0) {
2310 verticalScrollDirection = 1 /* UP */;
2311 }
2312 }
2313 else if (element.scrollHeight - scrollTop > element.clientHeight) {
2314 verticalScrollDirection = 2 /* DOWN */;
2315 }
2316 }
2317 if (computedHorizontal) {
2318 const scrollLeft = element.scrollLeft;
2319 if (computedHorizontal === 1 /* LEFT */) {
2320 if (scrollLeft > 0) {
2321 horizontalScrollDirection = 1 /* LEFT */;
2322 }
2323 }
2324 else if (element.scrollWidth - scrollLeft > element.clientWidth) {
2325 horizontalScrollDirection = 2 /* RIGHT */;
2326 }
2327 }
2328 return [verticalScrollDirection, horizontalScrollDirection];
2329}
2330
2331/**
2332 * @license
2333 * Copyright Google LLC All Rights Reserved.
2334 *
2335 * Use of this source code is governed by an MIT-style license that can be
2336 * found in the LICENSE file at https://angular.io/license
2337 */
2338/** Event options that can be used to bind an active, capturing event. */
2339const activeCapturingEventOptions = normalizePassiveListenerOptions({
2340 passive: false,
2341 capture: true
2342});
2343/**
2344 * Service that keeps track of all the drag item and drop container
2345 * instances, and manages global event listeners on the `document`.
2346 * @docs-private
2347 */
2348// Note: this class is generic, rather than referencing CdkDrag and CdkDropList directly, in order
2349// to avoid circular imports. If we were to reference them here, importing the registry into the
2350// classes that are registering themselves will introduce a circular import.
2351class DragDropRegistry {
2352 constructor(_ngZone, _document) {
2353 this._ngZone = _ngZone;
2354 /** Registered drop container instances. */
2355 this._dropInstances = new Set();
2356 /** Registered drag item instances. */
2357 this._dragInstances = new Set();
2358 /** Drag item instances that are currently being dragged. */
2359 this._activeDragInstances = [];
2360 /** Keeps track of the event listeners that we've bound to the `document`. */
2361 this._globalListeners = new Map();
2362 /**
2363 * Predicate function to check if an item is being dragged. Moved out into a property,
2364 * because it'll be called a lot and we don't want to create a new function every time.
2365 */
2366 this._draggingPredicate = (item) => item.isDragging();
2367 /**
2368 * Emits the `touchmove` or `mousemove` events that are dispatched
2369 * while the user is dragging a drag item instance.
2370 */
2371 this.pointerMove = new Subject();
2372 /**
2373 * Emits the `touchend` or `mouseup` events that are dispatched
2374 * while the user is dragging a drag item instance.
2375 */
2376 this.pointerUp = new Subject();
2377 /**
2378 * Emits when the viewport has been scrolled while the user is dragging an item.
2379 * @deprecated To be turned into a private member. Use the `scrolled` method instead.
2380 * @breaking-change 13.0.0
2381 */
2382 this.scroll = new Subject();
2383 /**
2384 * Event listener that will prevent the default browser action while the user is dragging.
2385 * @param event Event whose default action should be prevented.
2386 */
2387 this._preventDefaultWhileDragging = (event) => {
2388 if (this._activeDragInstances.length > 0) {
2389 event.preventDefault();
2390 }
2391 };
2392 /** Event listener for `touchmove` that is bound even if no dragging is happening. */
2393 this._persistentTouchmoveListener = (event) => {
2394 if (this._activeDragInstances.length > 0) {
2395 // Note that we only want to prevent the default action after dragging has actually started.
2396 // Usually this is the same time at which the item is added to the `_activeDragInstances`,
2397 // but it could be pushed back if the user has set up a drag delay or threshold.
2398 if (this._activeDragInstances.some(this._draggingPredicate)) {
2399 event.preventDefault();
2400 }
2401 this.pointerMove.next(event);
2402 }
2403 };
2404 this._document = _document;
2405 }
2406 /** Adds a drop container to the registry. */
2407 registerDropContainer(drop) {
2408 if (!this._dropInstances.has(drop)) {
2409 this._dropInstances.add(drop);
2410 }
2411 }
2412 /** Adds a drag item instance to the registry. */
2413 registerDragItem(drag) {
2414 this._dragInstances.add(drag);
2415 // The `touchmove` event gets bound once, ahead of time, because WebKit
2416 // won't preventDefault on a dynamically-added `touchmove` listener.
2417 // See https://bugs.webkit.org/show_bug.cgi?id=184250.
2418 if (this._dragInstances.size === 1) {
2419 this._ngZone.runOutsideAngular(() => {
2420 // The event handler has to be explicitly active,
2421 // because newer browsers make it passive by default.
2422 this._document.addEventListener('touchmove', this._persistentTouchmoveListener, activeCapturingEventOptions);
2423 });
2424 }
2425 }
2426 /** Removes a drop container from the registry. */
2427 removeDropContainer(drop) {
2428 this._dropInstances.delete(drop);
2429 }
2430 /** Removes a drag item instance from the registry. */
2431 removeDragItem(drag) {
2432 this._dragInstances.delete(drag);
2433 this.stopDragging(drag);
2434 if (this._dragInstances.size === 0) {
2435 this._document.removeEventListener('touchmove', this._persistentTouchmoveListener, activeCapturingEventOptions);
2436 }
2437 }
2438 /**
2439 * Starts the dragging sequence for a drag instance.
2440 * @param drag Drag instance which is being dragged.
2441 * @param event Event that initiated the dragging.
2442 */
2443 startDragging(drag, event) {
2444 // Do not process the same drag twice to avoid memory leaks and redundant listeners
2445 if (this._activeDragInstances.indexOf(drag) > -1) {
2446 return;
2447 }
2448 this._activeDragInstances.push(drag);
2449 if (this._activeDragInstances.length === 1) {
2450 const isTouchEvent = event.type.startsWith('touch');
2451 // We explicitly bind __active__ listeners here, because newer browsers will default to
2452 // passive ones for `mousemove` and `touchmove`. The events need to be active, because we
2453 // use `preventDefault` to prevent the page from scrolling while the user is dragging.
2454 this._globalListeners
2455 .set(isTouchEvent ? 'touchend' : 'mouseup', {
2456 handler: (e) => this.pointerUp.next(e),
2457 options: true
2458 })
2459 .set('scroll', {
2460 handler: (e) => this.scroll.next(e),
2461 // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
2462 // the document. See https://github.com/angular/components/issues/17144.
2463 options: true
2464 })
2465 // Preventing the default action on `mousemove` isn't enough to disable text selection
2466 // on Safari so we need to prevent the selection event as well. Alternatively this can
2467 // be done by setting `user-select: none` on the `body`, however it has causes a style
2468 // recalculation which can be expensive on pages with a lot of elements.
2469 .set('selectstart', {
2470 handler: this._preventDefaultWhileDragging,
2471 options: activeCapturingEventOptions
2472 });
2473 // We don't have to bind a move event for touch drag sequences, because
2474 // we already have a persistent global one bound from `registerDragItem`.
2475 if (!isTouchEvent) {
2476 this._globalListeners.set('mousemove', {
2477 handler: (e) => this.pointerMove.next(e),
2478 options: activeCapturingEventOptions
2479 });
2480 }
2481 this._ngZone.runOutsideAngular(() => {
2482 this._globalListeners.forEach((config, name) => {
2483 this._document.addEventListener(name, config.handler, config.options);
2484 });
2485 });
2486 }
2487 }
2488 /** Stops dragging a drag item instance. */
2489 stopDragging(drag) {
2490 const index = this._activeDragInstances.indexOf(drag);
2491 if (index > -1) {
2492 this._activeDragInstances.splice(index, 1);
2493 if (this._activeDragInstances.length === 0) {
2494 this._clearGlobalListeners();
2495 }
2496 }
2497 }
2498 /** Gets whether a drag item instance is currently being dragged. */
2499 isDragging(drag) {
2500 return this._activeDragInstances.indexOf(drag) > -1;
2501 }
2502 /**
2503 * Gets a stream that will emit when any element on the page is scrolled while an item is being
2504 * dragged.
2505 * @param shadowRoot Optional shadow root that the current dragging sequence started from.
2506 * Top-level listeners won't pick up events coming from the shadow DOM so this parameter can
2507 * be used to include an additional top-level listener at the shadow root level.
2508 */
2509 scrolled(shadowRoot) {
2510 const streams = [this.scroll];
2511 if (shadowRoot && shadowRoot !== this._document) {
2512 // Note that this is basically the same as `fromEvent` from rjxs, but we do it ourselves,
2513 // because we want to guarantee that the event is bound outside of the `NgZone`. With
2514 // `fromEvent` it'll only happen if the subscription is outside the `NgZone`.
2515 streams.push(new Observable((observer) => {
2516 return this._ngZone.runOutsideAngular(() => {
2517 const eventOptions = true;
2518 const callback = (event) => {
2519 if (this._activeDragInstances.length) {
2520 observer.next(event);
2521 }
2522 };
2523 shadowRoot.addEventListener('scroll', callback, eventOptions);
2524 return () => {
2525 shadowRoot.removeEventListener('scroll', callback, eventOptions);
2526 };
2527 });
2528 }));
2529 }
2530 return merge(...streams);
2531 }
2532 ngOnDestroy() {
2533 this._dragInstances.forEach(instance => this.removeDragItem(instance));
2534 this._dropInstances.forEach(instance => this.removeDropContainer(instance));
2535 this._clearGlobalListeners();
2536 this.pointerMove.complete();
2537 this.pointerUp.complete();
2538 }
2539 /** Clears out the global event listeners from the `document`. */
2540 _clearGlobalListeners() {
2541 this._globalListeners.forEach((config, name) => {
2542 this._document.removeEventListener(name, config.handler, config.options);
2543 });
2544 this._globalListeners.clear();
2545 }
2546}
2547DragDropRegistry.ɵprov = i0.ɵɵdefineInjectable({ factory: function DragDropRegistry_Factory() { return new DragDropRegistry(i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1.DOCUMENT)); }, token: DragDropRegistry, providedIn: "root" });
2548DragDropRegistry.decorators = [
2549 { type: Injectable, args: [{ providedIn: 'root' },] }
2550];
2551DragDropRegistry.ctorParameters = () => [
2552 { type: NgZone },
2553 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
2554];
2555
2556/**
2557 * @license
2558 * Copyright Google LLC All Rights Reserved.
2559 *
2560 * Use of this source code is governed by an MIT-style license that can be
2561 * found in the LICENSE file at https://angular.io/license
2562 */
2563/** Default configuration to be used when creating a `DragRef`. */
2564const DEFAULT_CONFIG = {
2565 dragStartThreshold: 5,
2566 pointerDirectionChangeThreshold: 5
2567};
2568/**
2569 * Service that allows for drag-and-drop functionality to be attached to DOM elements.
2570 */
2571class DragDrop {
2572 constructor(_document, _ngZone, _viewportRuler, _dragDropRegistry) {
2573 this._document = _document;
2574 this._ngZone = _ngZone;
2575 this._viewportRuler = _viewportRuler;
2576 this._dragDropRegistry = _dragDropRegistry;
2577 }
2578 /**
2579 * Turns an element into a draggable item.
2580 * @param element Element to which to attach the dragging functionality.
2581 * @param config Object used to configure the dragging behavior.
2582 */
2583 createDrag(element, config = DEFAULT_CONFIG) {
2584 return new DragRef(element, config, this._document, this._ngZone, this._viewportRuler, this._dragDropRegistry);
2585 }
2586 /**
2587 * Turns an element into a drop list.
2588 * @param element Element to which to attach the drop list functionality.
2589 */
2590 createDropList(element) {
2591 return new DropListRef(element, this._dragDropRegistry, this._document, this._ngZone, this._viewportRuler);
2592 }
2593}
2594DragDrop.ɵprov = i0.ɵɵdefineInjectable({ factory: function DragDrop_Factory() { return new DragDrop(i0.ɵɵinject(i1.DOCUMENT), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i2.ViewportRuler), i0.ɵɵinject(DragDropRegistry)); }, token: DragDrop, providedIn: "root" });
2595DragDrop.decorators = [
2596 { type: Injectable, args: [{ providedIn: 'root' },] }
2597];
2598DragDrop.ctorParameters = () => [
2599 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2600 { type: NgZone },
2601 { type: ViewportRuler },
2602 { type: DragDropRegistry }
2603];
2604
2605/**
2606 * @license
2607 * Copyright Google LLC All Rights Reserved.
2608 *
2609 * Use of this source code is governed by an MIT-style license that can be
2610 * found in the LICENSE file at https://angular.io/license
2611 */
2612/**
2613 * Injection token that can be used for a `CdkDrag` to provide itself as a parent to the
2614 * drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily
2615 * to avoid circular imports.
2616 * @docs-private
2617 */
2618const CDK_DRAG_PARENT = new InjectionToken('CDK_DRAG_PARENT');
2619
2620/**
2621 * @license
2622 * Copyright Google LLC All Rights Reserved.
2623 *
2624 * Use of this source code is governed by an MIT-style license that can be
2625 * found in the LICENSE file at https://angular.io/license
2626 */
2627
2628/**
2629 * @license
2630 * Copyright Google LLC All Rights Reserved.
2631 *
2632 * Use of this source code is governed by an MIT-style license that can be
2633 * found in the LICENSE file at https://angular.io/license
2634 */
2635/**
2636 * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as
2637 * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary
2638 * retention of the class and its directive metadata.
2639 */
2640const CDK_DROP_LIST_GROUP = new InjectionToken('CdkDropListGroup');
2641/**
2642 * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`
2643 * elements that are placed inside a `cdkDropListGroup` will be connected to each other
2644 * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input
2645 * from `cdkDropList`.
2646 */
2647class CdkDropListGroup {
2648 constructor() {
2649 /** Drop lists registered inside the group. */
2650 this._items = new Set();
2651 this._disabled = false;
2652 }
2653 /** Whether starting a dragging sequence from inside this group is disabled. */
2654 get disabled() { return this._disabled; }
2655 set disabled(value) {
2656 this._disabled = coerceBooleanProperty(value);
2657 }
2658 ngOnDestroy() {
2659 this._items.clear();
2660 }
2661}
2662CdkDropListGroup.decorators = [
2663 { type: Directive, args: [{
2664 selector: '[cdkDropListGroup]',
2665 exportAs: 'cdkDropListGroup',
2666 providers: [{ provide: CDK_DROP_LIST_GROUP, useExisting: CdkDropListGroup }],
2667 },] }
2668];
2669CdkDropListGroup.propDecorators = {
2670 disabled: [{ type: Input, args: ['cdkDropListGroupDisabled',] }]
2671};
2672
2673/**
2674 * @license
2675 * Copyright Google LLC All Rights Reserved.
2676 *
2677 * Use of this source code is governed by an MIT-style license that can be
2678 * found in the LICENSE file at https://angular.io/license
2679 */
2680/**
2681 * Injection token that can be used to configure the
2682 * behavior of the drag&drop-related components.
2683 */
2684const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG');
2685
2686/**
2687 * @license
2688 * Copyright Google LLC All Rights Reserved.
2689 *
2690 * Use of this source code is governed by an MIT-style license that can be
2691 * found in the LICENSE file at https://angular.io/license
2692 */
2693/**
2694 * Asserts that a particular node is an element.
2695 * @param node Node to be checked.
2696 * @param name Name to attach to the error message.
2697 */
2698function assertElementNode(node, name) {
2699 if (node.nodeType !== 1) {
2700 throw Error(`${name} must be attached to an element node. ` +
2701 `Currently attached to "${node.nodeName}".`);
2702 }
2703}
2704
2705/**
2706 * @license
2707 * Copyright Google LLC All Rights Reserved.
2708 *
2709 * Use of this source code is governed by an MIT-style license that can be
2710 * found in the LICENSE file at https://angular.io/license
2711 */
2712/** Counter used to generate unique ids for drop zones. */
2713let _uniqueIdCounter = 0;
2714/**
2715 * Injection token that can be used to reference instances of `CdkDropList`. It serves as
2716 * alternative token to the actual `CdkDropList` class which could cause unnecessary
2717 * retention of the class and its directive metadata.
2718 */
2719const CDK_DROP_LIST = new InjectionToken('CdkDropList');
2720const ɵ0 = undefined;
2721/** Container that wraps a set of draggable items. */
2722class CdkDropList {
2723 constructor(
2724 /** Element that the drop list is attached to. */
2725 element, dragDrop, _changeDetectorRef, _scrollDispatcher, _dir, _group, config) {
2726 this.element = element;
2727 this._changeDetectorRef = _changeDetectorRef;
2728 this._scrollDispatcher = _scrollDispatcher;
2729 this._dir = _dir;
2730 this._group = _group;
2731 /** Emits when the list has been destroyed. */
2732 this._destroyed = new Subject();
2733 /**
2734 * Other draggable containers that this container is connected to and into which the
2735 * container's items can be transferred. Can either be references to other drop containers,
2736 * or their unique IDs.
2737 */
2738 this.connectedTo = [];
2739 /**
2740 * Unique ID for the drop zone. Can be used as a reference
2741 * in the `connectedTo` of another `CdkDropList`.
2742 */
2743 this.id = `cdk-drop-list-${_uniqueIdCounter++}`;
2744 /**
2745 * Function that is used to determine whether an item
2746 * is allowed to be moved into a drop container.
2747 */
2748 this.enterPredicate = () => true;
2749 /** Functions that is used to determine whether an item can be sorted into a particular index. */
2750 this.sortPredicate = () => true;
2751 /** Emits when the user drops an item inside the container. */
2752 this.dropped = new EventEmitter();
2753 /**
2754 * Emits when the user has moved a new drag item into this container.
2755 */
2756 this.entered = new EventEmitter();
2757 /**
2758 * Emits when the user removes an item from the container
2759 * by dragging it into another container.
2760 */
2761 this.exited = new EventEmitter();
2762 /** Emits as the user is swapping items while actively dragging. */
2763 this.sorted = new EventEmitter();
2764 /**
2765 * Keeps track of the items that are registered with this container. Historically we used to
2766 * do this with a `ContentChildren` query, however queries don't handle transplanted views very
2767 * well which means that we can't handle cases like dragging the headers of a `mat-table`
2768 * correctly. What we do instead is to have the items register themselves with the container
2769 * and then we sort them based on their position in the DOM.
2770 */
2771 this._unsortedItems = new Set();
2772 if (typeof ngDevMode === 'undefined' || ngDevMode) {
2773 assertElementNode(element.nativeElement, 'cdkDropList');
2774 }
2775 this._dropListRef = dragDrop.createDropList(element);
2776 this._dropListRef.data = this;
2777 if (config) {
2778 this._assignDefaults(config);
2779 }
2780 this._dropListRef.enterPredicate = (drag, drop) => {
2781 return this.enterPredicate(drag.data, drop.data);
2782 };
2783 this._dropListRef.sortPredicate =
2784 (index, drag, drop) => {
2785 return this.sortPredicate(index, drag.data, drop.data);
2786 };
2787 this._setupInputSyncSubscription(this._dropListRef);
2788 this._handleEvents(this._dropListRef);
2789 CdkDropList._dropLists.push(this);
2790 if (_group) {
2791 _group._items.add(this);
2792 }
2793 }
2794 /** Whether starting a dragging sequence from this container is disabled. */
2795 get disabled() {
2796 return this._disabled || (!!this._group && this._group.disabled);
2797 }
2798 set disabled(value) {
2799 // Usually we sync the directive and ref state right before dragging starts, in order to have
2800 // a single point of failure and to avoid having to use setters for everything. `disabled` is
2801 // a special case, because it can prevent the `beforeStarted` event from firing, which can lock
2802 // the user in a disabled state, so we also need to sync it as it's being set.
2803 this._dropListRef.disabled = this._disabled = coerceBooleanProperty(value);
2804 }
2805 /** Registers an items with the drop list. */
2806 addItem(item) {
2807 this._unsortedItems.add(item);
2808 if (this._dropListRef.isDragging()) {
2809 this._syncItemsWithRef();
2810 }
2811 }
2812 /** Removes an item from the drop list. */
2813 removeItem(item) {
2814 this._unsortedItems.delete(item);
2815 if (this._dropListRef.isDragging()) {
2816 this._syncItemsWithRef();
2817 }
2818 }
2819 /** Gets the registered items in the list, sorted by their position in the DOM. */
2820 getSortedItems() {
2821 return Array.from(this._unsortedItems).sort((a, b) => {
2822 const documentPosition = a._dragRef.getVisibleElement().compareDocumentPosition(b._dragRef.getVisibleElement());
2823 // `compareDocumentPosition` returns a bitmask so we have to use a bitwise operator.
2824 // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
2825 // tslint:disable-next-line:no-bitwise
2826 return documentPosition & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
2827 });
2828 }
2829 ngOnDestroy() {
2830 const index = CdkDropList._dropLists.indexOf(this);
2831 if (index > -1) {
2832 CdkDropList._dropLists.splice(index, 1);
2833 }
2834 if (this._group) {
2835 this._group._items.delete(this);
2836 }
2837 this._unsortedItems.clear();
2838 this._dropListRef.dispose();
2839 this._destroyed.next();
2840 this._destroyed.complete();
2841 }
2842 /** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */
2843 _setupInputSyncSubscription(ref) {
2844 if (this._dir) {
2845 this._dir.change
2846 .pipe(startWith(this._dir.value), takeUntil(this._destroyed))
2847 .subscribe(value => ref.withDirection(value));
2848 }
2849 ref.beforeStarted.subscribe(() => {
2850 const siblings = coerceArray(this.connectedTo).map(drop => {
2851 if (typeof drop === 'string') {
2852 const correspondingDropList = CdkDropList._dropLists.find(list => list.id === drop);
2853 if (!correspondingDropList && (typeof ngDevMode === 'undefined' || ngDevMode)) {
2854 console.warn(`CdkDropList could not find connected drop list with id "${drop}"`);
2855 }
2856 return correspondingDropList;
2857 }
2858 return drop;
2859 });
2860 if (this._group) {
2861 this._group._items.forEach(drop => {
2862 if (siblings.indexOf(drop) === -1) {
2863 siblings.push(drop);
2864 }
2865 });
2866 }
2867 // Note that we resolve the scrollable parents here so that we delay the resolution
2868 // as long as possible, ensuring that the element is in its final place in the DOM.
2869 if (!this._scrollableParentsResolved) {
2870 const scrollableParents = this._scrollDispatcher
2871 .getAncestorScrollContainers(this.element)
2872 .map(scrollable => scrollable.getElementRef().nativeElement);
2873 this._dropListRef.withScrollableParents(scrollableParents);
2874 // Only do this once since it involves traversing the DOM and the parents
2875 // shouldn't be able to change without the drop list being destroyed.
2876 this._scrollableParentsResolved = true;
2877 }
2878 ref.disabled = this.disabled;
2879 ref.lockAxis = this.lockAxis;
2880 ref.sortingDisabled = coerceBooleanProperty(this.sortingDisabled);
2881 ref.autoScrollDisabled = coerceBooleanProperty(this.autoScrollDisabled);
2882 ref.autoScrollStep = coerceNumberProperty(this.autoScrollStep, 2);
2883 ref
2884 .connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
2885 .withOrientation(this.orientation);
2886 });
2887 }
2888 /** Handles events from the underlying DropListRef. */
2889 _handleEvents(ref) {
2890 ref.beforeStarted.subscribe(() => {
2891 this._syncItemsWithRef();
2892 this._changeDetectorRef.markForCheck();
2893 });
2894 ref.entered.subscribe(event => {
2895 this.entered.emit({
2896 container: this,
2897 item: event.item.data,
2898 currentIndex: event.currentIndex
2899 });
2900 });
2901 ref.exited.subscribe(event => {
2902 this.exited.emit({
2903 container: this,
2904 item: event.item.data
2905 });
2906 this._changeDetectorRef.markForCheck();
2907 });
2908 ref.sorted.subscribe(event => {
2909 this.sorted.emit({
2910 previousIndex: event.previousIndex,
2911 currentIndex: event.currentIndex,
2912 container: this,
2913 item: event.item.data
2914 });
2915 });
2916 ref.dropped.subscribe(event => {
2917 this.dropped.emit({
2918 previousIndex: event.previousIndex,
2919 currentIndex: event.currentIndex,
2920 previousContainer: event.previousContainer.data,
2921 container: event.container.data,
2922 item: event.item.data,
2923 isPointerOverContainer: event.isPointerOverContainer,
2924 distance: event.distance,
2925 dropPoint: event.dropPoint
2926 });
2927 // Mark for check since all of these events run outside of change
2928 // detection and we're not guaranteed for something else to have triggered it.
2929 this._changeDetectorRef.markForCheck();
2930 });
2931 }
2932 /** Assigns the default input values based on a provided config object. */
2933 _assignDefaults(config) {
2934 const { lockAxis, draggingDisabled, sortingDisabled, listAutoScrollDisabled, listOrientation } = config;
2935 this.disabled = draggingDisabled == null ? false : draggingDisabled;
2936 this.sortingDisabled = sortingDisabled == null ? false : sortingDisabled;
2937 this.autoScrollDisabled = listAutoScrollDisabled == null ? false : listAutoScrollDisabled;
2938 this.orientation = listOrientation || 'vertical';
2939 if (lockAxis) {
2940 this.lockAxis = lockAxis;
2941 }
2942 }
2943 /** Syncs up the registered drag items with underlying drop list ref. */
2944 _syncItemsWithRef() {
2945 this._dropListRef.withItems(this.getSortedItems().map(item => item._dragRef));
2946 }
2947}
2948/** Keeps track of the drop lists that are currently on the page. */
2949CdkDropList._dropLists = [];
2950CdkDropList.decorators = [
2951 { type: Directive, args: [{
2952 selector: '[cdkDropList], cdk-drop-list',
2953 exportAs: 'cdkDropList',
2954 providers: [
2955 // Prevent child drop lists from picking up the same group as their parent.
2956 { provide: CDK_DROP_LIST_GROUP, useValue: ɵ0 },
2957 { provide: CDK_DROP_LIST, useExisting: CdkDropList },
2958 ],
2959 host: {
2960 'class': 'cdk-drop-list',
2961 '[attr.id]': 'id',
2962 '[class.cdk-drop-list-disabled]': 'disabled',
2963 '[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()',
2964 '[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()',
2965 }
2966 },] }
2967];
2968CdkDropList.ctorParameters = () => [
2969 { type: ElementRef },
2970 { type: DragDrop },
2971 { type: ChangeDetectorRef },
2972 { type: ScrollDispatcher },
2973 { type: Directionality, decorators: [{ type: Optional }] },
2974 { type: CdkDropListGroup, decorators: [{ type: Optional }, { type: Inject, args: [CDK_DROP_LIST_GROUP,] }, { type: SkipSelf }] },
2975 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [CDK_DRAG_CONFIG,] }] }
2976];
2977CdkDropList.propDecorators = {
2978 connectedTo: [{ type: Input, args: ['cdkDropListConnectedTo',] }],
2979 data: [{ type: Input, args: ['cdkDropListData',] }],
2980 orientation: [{ type: Input, args: ['cdkDropListOrientation',] }],
2981 id: [{ type: Input }],
2982 lockAxis: [{ type: Input, args: ['cdkDropListLockAxis',] }],
2983 disabled: [{ type: Input, args: ['cdkDropListDisabled',] }],
2984 sortingDisabled: [{ type: Input, args: ['cdkDropListSortingDisabled',] }],
2985 enterPredicate: [{ type: Input, args: ['cdkDropListEnterPredicate',] }],
2986 sortPredicate: [{ type: Input, args: ['cdkDropListSortPredicate',] }],
2987 autoScrollDisabled: [{ type: Input, args: ['cdkDropListAutoScrollDisabled',] }],
2988 autoScrollStep: [{ type: Input, args: ['cdkDropListAutoScrollStep',] }],
2989 dropped: [{ type: Output, args: ['cdkDropListDropped',] }],
2990 entered: [{ type: Output, args: ['cdkDropListEntered',] }],
2991 exited: [{ type: Output, args: ['cdkDropListExited',] }],
2992 sorted: [{ type: Output, args: ['cdkDropListSorted',] }]
2993};
2994
2995/**
2996 * @license
2997 * Copyright Google LLC All Rights Reserved.
2998 *
2999 * Use of this source code is governed by an MIT-style license that can be
3000 * found in the LICENSE file at https://angular.io/license
3001 */
3002/**
3003 * Injection token that can be used to reference instances of `CdkDragHandle`. It serves as
3004 * alternative token to the actual `CdkDragHandle` class which could cause unnecessary
3005 * retention of the class and its directive metadata.
3006 */
3007const CDK_DRAG_HANDLE = new InjectionToken('CdkDragHandle');
3008/** Handle that can be used to drag a CdkDrag instance. */
3009class CdkDragHandle {
3010 constructor(element, parentDrag) {
3011 this.element = element;
3012 /** Emits when the state of the handle has changed. */
3013 this._stateChanges = new Subject();
3014 this._disabled = false;
3015 if (typeof ngDevMode === 'undefined' || ngDevMode) {
3016 assertElementNode(element.nativeElement, 'cdkDragHandle');
3017 }
3018 this._parentDrag = parentDrag;
3019 }
3020 /** Whether starting to drag through this handle is disabled. */
3021 get disabled() { return this._disabled; }
3022 set disabled(value) {
3023 this._disabled = coerceBooleanProperty(value);
3024 this._stateChanges.next(this);
3025 }
3026 ngOnDestroy() {
3027 this._stateChanges.complete();
3028 }
3029}
3030CdkDragHandle.decorators = [
3031 { type: Directive, args: [{
3032 selector: '[cdkDragHandle]',
3033 host: {
3034 'class': 'cdk-drag-handle'
3035 },
3036 providers: [{ provide: CDK_DRAG_HANDLE, useExisting: CdkDragHandle }],
3037 },] }
3038];
3039CdkDragHandle.ctorParameters = () => [
3040 { type: ElementRef },
3041 { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_PARENT,] }, { type: Optional }, { type: SkipSelf }] }
3042];
3043CdkDragHandle.propDecorators = {
3044 disabled: [{ type: Input, args: ['cdkDragHandleDisabled',] }]
3045};
3046
3047/**
3048 * @license
3049 * Copyright Google LLC All Rights Reserved.
3050 *
3051 * Use of this source code is governed by an MIT-style license that can be
3052 * found in the LICENSE file at https://angular.io/license
3053 */
3054/**
3055 * Injection token that can be used to reference instances of `CdkDragPlaceholder`. It serves as
3056 * alternative token to the actual `CdkDragPlaceholder` class which could cause unnecessary
3057 * retention of the class and its directive metadata.
3058 */
3059const CDK_DRAG_PLACEHOLDER = new InjectionToken('CdkDragPlaceholder');
3060/**
3061 * Element that will be used as a template for the placeholder of a CdkDrag when
3062 * it is being dragged. The placeholder is displayed in place of the element being dragged.
3063 */
3064class CdkDragPlaceholder {
3065 constructor(templateRef) {
3066 this.templateRef = templateRef;
3067 }
3068}
3069CdkDragPlaceholder.decorators = [
3070 { type: Directive, args: [{
3071 selector: 'ng-template[cdkDragPlaceholder]',
3072 providers: [{ provide: CDK_DRAG_PLACEHOLDER, useExisting: CdkDragPlaceholder }],
3073 },] }
3074];
3075CdkDragPlaceholder.ctorParameters = () => [
3076 { type: TemplateRef }
3077];
3078CdkDragPlaceholder.propDecorators = {
3079 data: [{ type: Input }]
3080};
3081
3082/**
3083 * @license
3084 * Copyright Google LLC All Rights Reserved.
3085 *
3086 * Use of this source code is governed by an MIT-style license that can be
3087 * found in the LICENSE file at https://angular.io/license
3088 */
3089/**
3090 * Injection token that can be used to reference instances of `CdkDragPreview`. It serves as
3091 * alternative token to the actual `CdkDragPreview` class which could cause unnecessary
3092 * retention of the class and its directive metadata.
3093 */
3094const CDK_DRAG_PREVIEW = new InjectionToken('CdkDragPreview');
3095/**
3096 * Element that will be used as a template for the preview
3097 * of a CdkDrag when it is being dragged.
3098 */
3099class CdkDragPreview {
3100 constructor(templateRef) {
3101 this.templateRef = templateRef;
3102 this._matchSize = false;
3103 }
3104 /** Whether the preview should preserve the same size as the item that is being dragged. */
3105 get matchSize() { return this._matchSize; }
3106 set matchSize(value) { this._matchSize = coerceBooleanProperty(value); }
3107}
3108CdkDragPreview.decorators = [
3109 { type: Directive, args: [{
3110 selector: 'ng-template[cdkDragPreview]',
3111 providers: [{ provide: CDK_DRAG_PREVIEW, useExisting: CdkDragPreview }],
3112 },] }
3113];
3114CdkDragPreview.ctorParameters = () => [
3115 { type: TemplateRef }
3116];
3117CdkDragPreview.propDecorators = {
3118 data: [{ type: Input }],
3119 matchSize: [{ type: Input }]
3120};
3121
3122/**
3123 * @license
3124 * Copyright Google LLC All Rights Reserved.
3125 *
3126 * Use of this source code is governed by an MIT-style license that can be
3127 * found in the LICENSE file at https://angular.io/license
3128 */
3129const DRAG_HOST_CLASS = 'cdk-drag';
3130/** Element that can be moved inside a CdkDropList container. */
3131class CdkDrag {
3132 constructor(
3133 /** Element that the draggable is attached to. */
3134 element,
3135 /** Droppable container that the draggable is a part of. */
3136 dropContainer,
3137 /**
3138 * @deprecated `_document` parameter no longer being used and will be removed.
3139 * @breaking-change 12.0.0
3140 */
3141 _document, _ngZone, _viewContainerRef, config, _dir, dragDrop, _changeDetectorRef, _selfHandle, _parentDrag) {
3142 this.element = element;
3143 this.dropContainer = dropContainer;
3144 this._ngZone = _ngZone;
3145 this._viewContainerRef = _viewContainerRef;
3146 this._dir = _dir;
3147 this._changeDetectorRef = _changeDetectorRef;
3148 this._selfHandle = _selfHandle;
3149 this._parentDrag = _parentDrag;
3150 this._destroyed = new Subject();
3151 /** Emits when the user starts dragging the item. */
3152 this.started = new EventEmitter();
3153 /** Emits when the user has released a drag item, before any animations have started. */
3154 this.released = new EventEmitter();
3155 /** Emits when the user stops dragging an item in the container. */
3156 this.ended = new EventEmitter();
3157 /** Emits when the user has moved the item into a new container. */
3158 this.entered = new EventEmitter();
3159 /** Emits when the user removes the item its container by dragging it into another container. */
3160 this.exited = new EventEmitter();
3161 /** Emits when the user drops the item inside a container. */
3162 this.dropped = new EventEmitter();
3163 /**
3164 * Emits as the user is dragging the item. Use with caution,
3165 * because this event will fire for every pixel that the user has dragged.
3166 */
3167 this.moved = new Observable((observer) => {
3168 const subscription = this._dragRef.moved.pipe(map(movedEvent => ({
3169 source: this,
3170 pointerPosition: movedEvent.pointerPosition,
3171 event: movedEvent.event,
3172 delta: movedEvent.delta,
3173 distance: movedEvent.distance
3174 }))).subscribe(observer);
3175 return () => {
3176 subscription.unsubscribe();
3177 };
3178 });
3179 this._dragRef = dragDrop.createDrag(element, {
3180 dragStartThreshold: config && config.dragStartThreshold != null ?
3181 config.dragStartThreshold : 5,
3182 pointerDirectionChangeThreshold: config && config.pointerDirectionChangeThreshold != null ?
3183 config.pointerDirectionChangeThreshold : 5,
3184 zIndex: config === null || config === void 0 ? void 0 : config.zIndex,
3185 });
3186 this._dragRef.data = this;
3187 // We have to keep track of the drag instances in order to be able to match an element to
3188 // a drag instance. We can't go through the global registry of `DragRef`, because the root
3189 // element could be different.
3190 CdkDrag._dragInstances.push(this);
3191 if (config) {
3192 this._assignDefaults(config);
3193 }
3194 // Note that usually the container is assigned when the drop list is picks up the item, but in
3195 // some cases (mainly transplanted views with OnPush, see #18341) we may end up in a situation
3196 // where there are no items on the first change detection pass, but the items get picked up as
3197 // soon as the user triggers another pass by dragging. This is a problem, because the item would
3198 // have to switch from standalone mode to drag mode in the middle of the dragging sequence which
3199 // is too late since the two modes save different kinds of information. We work around it by
3200 // assigning the drop container both from here and the list.
3201 if (dropContainer) {
3202 this._dragRef._withDropContainer(dropContainer._dropListRef);
3203 dropContainer.addItem(this);
3204 }
3205 this._syncInputs(this._dragRef);
3206 this._handleEvents(this._dragRef);
3207 }
3208 /** Whether starting to drag this element is disabled. */
3209 get disabled() {
3210 return this._disabled || (this.dropContainer && this.dropContainer.disabled);
3211 }
3212 set disabled(value) {
3213 this._disabled = coerceBooleanProperty(value);
3214 this._dragRef.disabled = this._disabled;
3215 }
3216 /**
3217 * Returns the element that is being used as a placeholder
3218 * while the current element is being dragged.
3219 */
3220 getPlaceholderElement() {
3221 return this._dragRef.getPlaceholderElement();
3222 }
3223 /** Returns the root draggable element. */
3224 getRootElement() {
3225 return this._dragRef.getRootElement();
3226 }
3227 /** Resets a standalone drag item to its initial position. */
3228 reset() {
3229 this._dragRef.reset();
3230 }
3231 /**
3232 * Gets the pixel coordinates of the draggable outside of a drop container.
3233 */
3234 getFreeDragPosition() {
3235 return this._dragRef.getFreeDragPosition();
3236 }
3237 ngAfterViewInit() {
3238 // Normally this isn't in the zone, but it can cause major performance regressions for apps
3239 // using `zone-patch-rxjs` because it'll trigger a change detection when it unsubscribes.
3240 this._ngZone.runOutsideAngular(() => {
3241 // We need to wait for the zone to stabilize, in order for the reference
3242 // element to be in the proper place in the DOM. This is mostly relevant
3243 // for draggable elements inside portals since they get stamped out in
3244 // their original DOM position and then they get transferred to the portal.
3245 this._ngZone.onStable
3246 .pipe(take(1), takeUntil(this._destroyed))
3247 .subscribe(() => {
3248 this._updateRootElement();
3249 this._setupHandlesListener();
3250 if (this.freeDragPosition) {
3251 this._dragRef.setFreeDragPosition(this.freeDragPosition);
3252 }
3253 });
3254 });
3255 }
3256 ngOnChanges(changes) {
3257 const rootSelectorChange = changes['rootElementSelector'];
3258 const positionChange = changes['freeDragPosition'];
3259 // We don't have to react to the first change since it's being
3260 // handled in `ngAfterViewInit` where it needs to be deferred.
3261 if (rootSelectorChange && !rootSelectorChange.firstChange) {
3262 this._updateRootElement();
3263 }
3264 // Skip the first change since it's being handled in `ngAfterViewInit`.
3265 if (positionChange && !positionChange.firstChange && this.freeDragPosition) {
3266 this._dragRef.setFreeDragPosition(this.freeDragPosition);
3267 }
3268 }
3269 ngOnDestroy() {
3270 if (this.dropContainer) {
3271 this.dropContainer.removeItem(this);
3272 }
3273 const index = CdkDrag._dragInstances.indexOf(this);
3274 if (index > -1) {
3275 CdkDrag._dragInstances.splice(index, 1);
3276 }
3277 // Unnecessary in most cases, but used to avoid extra change detections with `zone-paths-rxjs`.
3278 this._ngZone.runOutsideAngular(() => {
3279 this._destroyed.next();
3280 this._destroyed.complete();
3281 this._dragRef.dispose();
3282 });
3283 }
3284 /** Syncs the root element with the `DragRef`. */
3285 _updateRootElement() {
3286 const element = this.element.nativeElement;
3287 const rootElement = this.rootElementSelector ?
3288 getClosestMatchingAncestor(element, this.rootElementSelector) : element;
3289 if (rootElement && (typeof ngDevMode === 'undefined' || ngDevMode)) {
3290 assertElementNode(rootElement, 'cdkDrag');
3291 }
3292 this._dragRef.withRootElement(rootElement || element);
3293 }
3294 /** Gets the boundary element, based on the `boundaryElement` value. */
3295 _getBoundaryElement() {
3296 const boundary = this.boundaryElement;
3297 if (!boundary) {
3298 return null;
3299 }
3300 if (typeof boundary === 'string') {
3301 return getClosestMatchingAncestor(this.element.nativeElement, boundary);
3302 }
3303 const element = coerceElement(boundary);
3304 if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
3305 !element.contains(this.element.nativeElement)) {
3306 throw Error('Draggable element is not inside of the node passed into cdkDragBoundary.');
3307 }
3308 return element;
3309 }
3310 /** Syncs the inputs of the CdkDrag with the options of the underlying DragRef. */
3311 _syncInputs(ref) {
3312 ref.beforeStarted.subscribe(() => {
3313 if (!ref.isDragging()) {
3314 const dir = this._dir;
3315 const dragStartDelay = this.dragStartDelay;
3316 const placeholder = this._placeholderTemplate ? {
3317 template: this._placeholderTemplate.templateRef,
3318 context: this._placeholderTemplate.data,
3319 viewContainer: this._viewContainerRef
3320 } : null;
3321 const preview = this._previewTemplate ? {
3322 template: this._previewTemplate.templateRef,
3323 context: this._previewTemplate.data,
3324 matchSize: this._previewTemplate.matchSize,
3325 viewContainer: this._viewContainerRef
3326 } : null;
3327 ref.disabled = this.disabled;
3328 ref.lockAxis = this.lockAxis;
3329 ref.dragStartDelay = (typeof dragStartDelay === 'object' && dragStartDelay) ?
3330 dragStartDelay : coerceNumberProperty(dragStartDelay);
3331 ref.constrainPosition = this.constrainPosition;
3332 ref.previewClass = this.previewClass;
3333 ref
3334 .withBoundaryElement(this._getBoundaryElement())
3335 .withPlaceholderTemplate(placeholder)
3336 .withPreviewTemplate(preview)
3337 .withPreviewContainer(this.previewContainer || 'global');
3338 if (dir) {
3339 ref.withDirection(dir.value);
3340 }
3341 }
3342 });
3343 // This only needs to be resolved once.
3344 ref.beforeStarted.pipe(take(1)).subscribe(() => {
3345 var _a, _b;
3346 // If we managed to resolve a parent through DI, use it.
3347 if (this._parentDrag) {
3348 ref.withParent(this._parentDrag._dragRef);
3349 return;
3350 }
3351 // Otherwise fall back to resolving the parent by looking up the DOM. This can happen if
3352 // the item was projected into another item by something like `ngTemplateOutlet`.
3353 let parent = this.element.nativeElement.parentElement;
3354 while (parent) {
3355 // `classList` needs to be null checked, because IE doesn't have it on some elements.
3356 if ((_a = parent.classList) === null || _a === void 0 ? void 0 : _a.contains(DRAG_HOST_CLASS)) {
3357 ref.withParent(((_b = CdkDrag._dragInstances.find(drag => {
3358 return drag.element.nativeElement === parent;
3359 })) === null || _b === void 0 ? void 0 : _b._dragRef) || null);
3360 break;
3361 }
3362 parent = parent.parentElement;
3363 }
3364 });
3365 }
3366 /** Handles the events from the underlying `DragRef`. */
3367 _handleEvents(ref) {
3368 ref.started.subscribe(() => {
3369 this.started.emit({ source: this });
3370 // Since all of these events run outside of change detection,
3371 // we need to ensure that everything is marked correctly.
3372 this._changeDetectorRef.markForCheck();
3373 });
3374 ref.released.subscribe(() => {
3375 this.released.emit({ source: this });
3376 });
3377 ref.ended.subscribe(event => {
3378 this.ended.emit({
3379 source: this,
3380 distance: event.distance,
3381 dropPoint: event.dropPoint
3382 });
3383 // Since all of these events run outside of change detection,
3384 // we need to ensure that everything is marked correctly.
3385 this._changeDetectorRef.markForCheck();
3386 });
3387 ref.entered.subscribe(event => {
3388 this.entered.emit({
3389 container: event.container.data,
3390 item: this,
3391 currentIndex: event.currentIndex
3392 });
3393 });
3394 ref.exited.subscribe(event => {
3395 this.exited.emit({
3396 container: event.container.data,
3397 item: this
3398 });
3399 });
3400 ref.dropped.subscribe(event => {
3401 this.dropped.emit({
3402 previousIndex: event.previousIndex,
3403 currentIndex: event.currentIndex,
3404 previousContainer: event.previousContainer.data,
3405 container: event.container.data,
3406 isPointerOverContainer: event.isPointerOverContainer,
3407 item: this,
3408 distance: event.distance,
3409 dropPoint: event.dropPoint
3410 });
3411 });
3412 }
3413 /** Assigns the default input values based on a provided config object. */
3414 _assignDefaults(config) {
3415 const { lockAxis, dragStartDelay, constrainPosition, previewClass, boundaryElement, draggingDisabled, rootElementSelector, previewContainer } = config;
3416 this.disabled = draggingDisabled == null ? false : draggingDisabled;
3417 this.dragStartDelay = dragStartDelay || 0;
3418 if (lockAxis) {
3419 this.lockAxis = lockAxis;
3420 }
3421 if (constrainPosition) {
3422 this.constrainPosition = constrainPosition;
3423 }
3424 if (previewClass) {
3425 this.previewClass = previewClass;
3426 }
3427 if (boundaryElement) {
3428 this.boundaryElement = boundaryElement;
3429 }
3430 if (rootElementSelector) {
3431 this.rootElementSelector = rootElementSelector;
3432 }
3433 if (previewContainer) {
3434 this.previewContainer = previewContainer;
3435 }
3436 }
3437 /** Sets up the listener that syncs the handles with the drag ref. */
3438 _setupHandlesListener() {
3439 // Listen for any newly-added handles.
3440 this._handles.changes.pipe(startWith(this._handles),
3441 // Sync the new handles with the DragRef.
3442 tap((handles) => {
3443 const childHandleElements = handles
3444 .filter(handle => handle._parentDrag === this)
3445 .map(handle => handle.element);
3446 // Usually handles are only allowed to be a descendant of the drag element, but if
3447 // the consumer defined a different drag root, we should allow the drag element
3448 // itself to be a handle too.
3449 if (this._selfHandle && this.rootElementSelector) {
3450 childHandleElements.push(this.element);
3451 }
3452 this._dragRef.withHandles(childHandleElements);
3453 }),
3454 // Listen if the state of any of the handles changes.
3455 switchMap((handles) => {
3456 return merge(...handles.map(item => {
3457 return item._stateChanges.pipe(startWith(item));
3458 }));
3459 }), takeUntil(this._destroyed)).subscribe(handleInstance => {
3460 // Enabled/disable the handle that changed in the DragRef.
3461 const dragRef = this._dragRef;
3462 const handle = handleInstance.element.nativeElement;
3463 handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle);
3464 });
3465 }
3466}
3467CdkDrag._dragInstances = [];
3468CdkDrag.decorators = [
3469 { type: Directive, args: [{
3470 selector: '[cdkDrag]',
3471 exportAs: 'cdkDrag',
3472 host: {
3473 'class': DRAG_HOST_CLASS,
3474 '[class.cdk-drag-disabled]': 'disabled',
3475 '[class.cdk-drag-dragging]': '_dragRef.isDragging()',
3476 },
3477 providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }]
3478 },] }
3479];
3480CdkDrag.ctorParameters = () => [
3481 { type: ElementRef },
3482 { type: undefined, decorators: [{ type: Inject, args: [CDK_DROP_LIST,] }, { type: Optional }, { type: SkipSelf }] },
3483 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
3484 { type: NgZone },
3485 { type: ViewContainerRef },
3486 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [CDK_DRAG_CONFIG,] }] },
3487 { type: Directionality, decorators: [{ type: Optional }] },
3488 { type: DragDrop },
3489 { type: ChangeDetectorRef },
3490 { type: CdkDragHandle, decorators: [{ type: Optional }, { type: Self }, { type: Inject, args: [CDK_DRAG_HANDLE,] }] },
3491 { type: CdkDrag, decorators: [{ type: Optional }, { type: SkipSelf }, { type: Inject, args: [CDK_DRAG_PARENT,] }] }
3492];
3493CdkDrag.propDecorators = {
3494 _handles: [{ type: ContentChildren, args: [CDK_DRAG_HANDLE, { descendants: true },] }],
3495 _previewTemplate: [{ type: ContentChild, args: [CDK_DRAG_PREVIEW,] }],
3496 _placeholderTemplate: [{ type: ContentChild, args: [CDK_DRAG_PLACEHOLDER,] }],
3497 data: [{ type: Input, args: ['cdkDragData',] }],
3498 lockAxis: [{ type: Input, args: ['cdkDragLockAxis',] }],
3499 rootElementSelector: [{ type: Input, args: ['cdkDragRootElement',] }],
3500 boundaryElement: [{ type: Input, args: ['cdkDragBoundary',] }],
3501 dragStartDelay: [{ type: Input, args: ['cdkDragStartDelay',] }],
3502 freeDragPosition: [{ type: Input, args: ['cdkDragFreeDragPosition',] }],
3503 disabled: [{ type: Input, args: ['cdkDragDisabled',] }],
3504 constrainPosition: [{ type: Input, args: ['cdkDragConstrainPosition',] }],
3505 previewClass: [{ type: Input, args: ['cdkDragPreviewClass',] }],
3506 previewContainer: [{ type: Input, args: ['cdkDragPreviewContainer',] }],
3507 started: [{ type: Output, args: ['cdkDragStarted',] }],
3508 released: [{ type: Output, args: ['cdkDragReleased',] }],
3509 ended: [{ type: Output, args: ['cdkDragEnded',] }],
3510 entered: [{ type: Output, args: ['cdkDragEntered',] }],
3511 exited: [{ type: Output, args: ['cdkDragExited',] }],
3512 dropped: [{ type: Output, args: ['cdkDragDropped',] }],
3513 moved: [{ type: Output, args: ['cdkDragMoved',] }]
3514};
3515/** Gets the closest ancestor of an element that matches a selector. */
3516function getClosestMatchingAncestor(element, selector) {
3517 let currentElement = element.parentElement;
3518 while (currentElement) {
3519 // IE doesn't support `matches` so we have to fall back to `msMatchesSelector`.
3520 if (currentElement.matches ? currentElement.matches(selector) :
3521 currentElement.msMatchesSelector(selector)) {
3522 return currentElement;
3523 }
3524 currentElement = currentElement.parentElement;
3525 }
3526 return null;
3527}
3528
3529/**
3530 * @license
3531 * Copyright Google LLC All Rights Reserved.
3532 *
3533 * Use of this source code is governed by an MIT-style license that can be
3534 * found in the LICENSE file at https://angular.io/license
3535 */
3536class DragDropModule {
3537}
3538DragDropModule.decorators = [
3539 { type: NgModule, args: [{
3540 declarations: [
3541 CdkDropList,
3542 CdkDropListGroup,
3543 CdkDrag,
3544 CdkDragHandle,
3545 CdkDragPreview,
3546 CdkDragPlaceholder,
3547 ],
3548 exports: [
3549 CdkScrollableModule,
3550 CdkDropList,
3551 CdkDropListGroup,
3552 CdkDrag,
3553 CdkDragHandle,
3554 CdkDragPreview,
3555 CdkDragPlaceholder,
3556 ],
3557 providers: [
3558 DragDrop,
3559 ]
3560 },] }
3561];
3562
3563/**
3564 * @license
3565 * Copyright Google LLC All Rights Reserved.
3566 *
3567 * Use of this source code is governed by an MIT-style license that can be
3568 * found in the LICENSE file at https://angular.io/license
3569 */
3570
3571/**
3572 * Generated bundle index. Do not edit.
3573 */
3574
3575export { CDK_DRAG_CONFIG, CDK_DRAG_HANDLE, CDK_DRAG_PARENT, CDK_DRAG_PLACEHOLDER, CDK_DRAG_PREVIEW, CDK_DROP_LIST, CDK_DROP_LIST_GROUP, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDragPreview, CdkDropList, CdkDropListGroup, DragDrop, DragDropModule, DragDropRegistry, DragRef, DropListRef, copyArrayItem, moveItemInArray, transferArrayItem };
3576//# sourceMappingURL=drag-drop.js.map
Note: See TracBrowser for help on using the repository browser.