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

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

adding new components

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