@angular/cdk 7.2.2 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/bundles/cdk-drag-drop.umd.js +2798 -2597
  2. package/bundles/cdk-drag-drop.umd.js.map +1 -1
  3. package/bundles/cdk-drag-drop.umd.min.js +2 -2
  4. package/bundles/cdk-drag-drop.umd.min.js.map +1 -1
  5. package/bundles/cdk-overlay.umd.js +86 -15
  6. package/bundles/cdk-overlay.umd.js.map +1 -1
  7. package/bundles/cdk-overlay.umd.min.js +2 -2
  8. package/bundles/cdk-overlay.umd.min.js.map +1 -1
  9. package/bundles/cdk-stepper.umd.js +2 -2
  10. package/bundles/cdk-stepper.umd.js.map +1 -1
  11. package/bundles/cdk-stepper.umd.min.js.map +1 -1
  12. package/bundles/cdk.umd.js +1 -1
  13. package/bundles/cdk.umd.js.map +1 -1
  14. package/bundles/cdk.umd.min.js +1 -1
  15. package/bundles/cdk.umd.min.js.map +1 -1
  16. package/drag-drop/typings/directives/drag.d.ts +8 -4
  17. package/drag-drop/typings/directives/drop-list.d.ts +17 -3
  18. package/drag-drop/typings/drag-drop.d.ts +33 -0
  19. package/drag-drop/typings/drag-ref.d.ts +20 -13
  20. package/drag-drop/typings/drop-list-ref.d.ts +8 -4
  21. package/drag-drop/typings/index.d.ts +2 -2
  22. package/drag-drop/typings/index.metadata.json +1 -1
  23. package/drag-drop/typings/public-api.d.ts +4 -1
  24. package/esm2015/cdk.js +1 -1
  25. package/esm2015/cdk.js.map +1 -1
  26. package/esm2015/drag-drop.js +2213 -2058
  27. package/esm2015/drag-drop.js.map +1 -1
  28. package/esm2015/overlay.js +58 -11
  29. package/esm2015/overlay.js.map +1 -1
  30. package/esm2015/stepper.js +1 -1
  31. package/esm2015/stepper.js.map +1 -1
  32. package/esm5/cdk.es5.js +1 -1
  33. package/esm5/cdk.es5.js.map +1 -1
  34. package/esm5/drag-drop.es5.js +2792 -2594
  35. package/esm5/drag-drop.es5.js.map +1 -1
  36. package/esm5/overlay.es5.js +88 -17
  37. package/esm5/overlay.es5.js.map +1 -1
  38. package/esm5/stepper.es5.js +2 -2
  39. package/esm5/stepper.es5.js.map +1 -1
  40. package/overlay/typings/index.metadata.json +1 -1
  41. package/overlay/typings/overlay-ref.d.ts +4 -0
  42. package/overlay/typings/position/flexible-connected-position-strategy.d.ts +17 -4
  43. package/overlay/typings/position/overlay-position-builder.d.ts +3 -3
  44. package/package.json +4 -4
  45. package/stepper/typings/stepper.d.ts +1 -1
  46. package/typings/drag-drop/directives/drag.d.ts +8 -4
  47. package/typings/drag-drop/directives/drop-list.d.ts +17 -3
  48. package/typings/drag-drop/drag-drop.d.ts +33 -0
  49. package/typings/drag-drop/drag-ref.d.ts +20 -13
  50. package/typings/drag-drop/drop-list-ref.d.ts +8 -4
  51. package/typings/drag-drop/index.d.ts +2 -2
  52. package/typings/drag-drop/index.metadata.json +1 -1
  53. package/typings/drag-drop/public-api.d.ts +4 -1
  54. package/typings/esm5/drag-drop/directives/drag.d.ts +8 -4
  55. package/typings/esm5/drag-drop/directives/drop-list.d.ts +17 -3
  56. package/typings/esm5/drag-drop/drag-drop.d.ts +33 -0
  57. package/typings/esm5/drag-drop/drag-ref.d.ts +20 -13
  58. package/typings/esm5/drag-drop/drop-list-ref.d.ts +8 -4
  59. package/typings/esm5/drag-drop/index.d.ts +2 -2
  60. package/typings/esm5/drag-drop/index.metadata.json +1 -1
  61. package/typings/esm5/drag-drop/public-api.d.ts +4 -1
  62. package/typings/esm5/index.metadata.json +1 -1
  63. package/typings/esm5/overlay/index.metadata.json +1 -1
  64. package/typings/esm5/overlay/overlay-ref.d.ts +4 -0
  65. package/typings/esm5/overlay/position/flexible-connected-position-strategy.d.ts +17 -4
  66. package/typings/esm5/overlay/position/overlay-position-builder.d.ts +3 -3
  67. package/typings/esm5/stepper/stepper.d.ts +1 -1
  68. package/typings/index.metadata.json +1 -1
  69. package/typings/overlay/index.metadata.json +1 -1
  70. package/typings/overlay/overlay-ref.d.ts +4 -0
  71. package/typings/overlay/position/flexible-connected-position-strategy.d.ts +17 -4
  72. package/typings/overlay/position/overlay-position-builder.d.ts +3 -3
  73. package/typings/stepper/stepper.d.ts +1 -1
@@ -5,13 +5,13 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
- import { InjectionToken, Injectable, NgZone, Inject, NgModule, ContentChildren, ElementRef, EventEmitter, forwardRef, Input, Output, Optional, Directive, ChangeDetectorRef, SkipSelf, ContentChild, ViewContainerRef, TemplateRef, defineInjectable, inject } from '@angular/core';
9
- import { DOCUMENT } from '@angular/common';
10
8
  import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
11
- import { Subject, Subscription, Observable, merge } from 'rxjs';
12
9
  import { coerceBooleanProperty, coerceElement, coerceArray } from '@angular/cdk/coercion';
13
- import { Directionality } from '@angular/cdk/bidi';
10
+ import { Subscription, Subject, Observable, merge } from 'rxjs';
11
+ import { ElementRef, Injectable, NgZone, Inject, InjectionToken, NgModule, ContentChildren, EventEmitter, forwardRef, Input, Output, Optional, Directive, ChangeDetectorRef, SkipSelf, ContentChild, ViewContainerRef, TemplateRef, defineInjectable, inject } from '@angular/core';
12
+ import { DOCUMENT } from '@angular/common';
14
13
  import { ViewportRuler } from '@angular/cdk/scrolling';
14
+ import { Directionality } from '@angular/cdk/bidi';
15
15
  import { startWith, take, map, takeUntil, switchMap, tap } from 'rxjs/operators';
16
16
 
17
17
  /**
@@ -19,24 +19,40 @@ import { startWith, take, map, takeUntil, switchMap, tap } from 'rxjs/operators'
19
19
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
20
20
  */
21
21
  /**
22
- * Injection token that is used to provide a CdkDropList instance to CdkDrag.
23
- * Used for avoiding circular imports.
24
- * @type {?}
25
- */
26
- const CDK_DROP_LIST = new InjectionToken('CDK_DROP_LIST');
27
- /**
28
- * Injection token that is used to provide a CdkDropList instance to CdkDrag.
29
- * Used for avoiding circular imports.
30
- * @deprecated Use `CDK_DROP_LIST` instead.
31
- * \@breaking-change 8.0.0
32
- * @type {?}
22
+ * Shallow-extends a stylesheet object with another stylesheet object.
23
+ * \@docs-private
24
+ * @param {?} dest
25
+ * @param {?} source
26
+ * @return {?}
33
27
  */
34
- const CDK_DROP_LIST_CONTAINER = CDK_DROP_LIST;
35
-
28
+ function extendStyles(dest, source) {
29
+ for (let key in source) {
30
+ if (source.hasOwnProperty(key)) {
31
+ dest[(/** @type {?} */ (key))] = source[(/** @type {?} */ (key))];
32
+ }
33
+ }
34
+ return dest;
35
+ }
36
36
  /**
37
- * @fileoverview added by tsickle
38
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
37
+ * Toggles whether the native drag interactions should be enabled for an element.
38
+ * \@docs-private
39
+ * @param {?} element Element on which to toggle the drag interactions.
40
+ * @param {?} enable Whether the drag interactions should be enabled.
41
+ * @return {?}
39
42
  */
43
+ function toggleNativeDragInteractions(element, enable) {
44
+ /** @type {?} */
45
+ const userSelect = enable ? '' : 'none';
46
+ extendStyles(element.style, {
47
+ touchAction: enable ? '' : 'none',
48
+ webkitUserDrag: enable ? '' : 'none',
49
+ webkitTapHighlightColor: enable ? '' : 'transparent',
50
+ userSelect: userSelect,
51
+ msUserSelect: userSelect,
52
+ webkitUserSelect: userSelect,
53
+ MozUserSelect: userSelect
54
+ });
55
+ }
40
56
 
41
57
  /**
42
58
  * @fileoverview added by tsickle
@@ -44,74 +60,53 @@ const CDK_DROP_LIST_CONTAINER = CDK_DROP_LIST;
44
60
  */
45
61
 
46
62
  /**
47
- * Moves an item one index in an array to another.
48
- * @template T
49
- * @param {?} array Array in which to move the item.
50
- * @param {?} fromIndex Starting index of the item.
51
- * @param {?} toIndex Index to which the item should be moved.
63
+ * Parses a CSS time value to milliseconds.
64
+ * @param {?} value
52
65
  * @return {?}
53
66
  */
54
- function moveItemInArray(array, fromIndex, toIndex) {
55
- /** @type {?} */
56
- const from = clamp(fromIndex, array.length - 1);
57
- /** @type {?} */
58
- const to = clamp(toIndex, array.length - 1);
59
- if (from === to) {
60
- return;
61
- }
62
- /** @type {?} */
63
- const target = array[from];
67
+ function parseCssTimeUnitsToMs(value) {
68
+ // Some browsers will return it in seconds, whereas others will return milliseconds.
64
69
  /** @type {?} */
65
- const delta = to < from ? -1 : 1;
66
- for (let i = from; i !== to; i += delta) {
67
- array[i] = array[i + delta];
68
- }
69
- array[to] = target;
70
+ const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;
71
+ return parseFloat(value) * multiplier;
70
72
  }
71
73
  /**
72
- * Moves an item from one array to another.
73
- * @template T
74
- * @param {?} currentArray Array from which to transfer the item.
75
- * @param {?} targetArray Array into which to put the item.
76
- * @param {?} currentIndex Index of the item in its current array.
77
- * @param {?} targetIndex Index at which to insert the item.
74
+ * Gets the transform transition duration, including the delay, of an element in milliseconds.
75
+ * @param {?} element
78
76
  * @return {?}
79
77
  */
80
- function transferArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
78
+ function getTransformTransitionDurationInMs(element) {
81
79
  /** @type {?} */
82
- const from = clamp(currentIndex, currentArray.length - 1);
80
+ const computedStyle = getComputedStyle(element);
83
81
  /** @type {?} */
84
- const to = clamp(targetIndex, targetArray.length);
85
- if (currentArray.length) {
86
- targetArray.splice(to, 0, currentArray.splice(from, 1)[0]);
87
- }
88
- }
89
- /**
90
- * Copies an item from one array to another, leaving it in its
91
- * original position in current array.
92
- * @template T
93
- * @param {?} currentArray Array from which to copy the item.
94
- * @param {?} targetArray Array into which is copy the item.
95
- * @param {?} currentIndex Index of the item in its current array.
96
- * @param {?} targetIndex Index at which to insert the item.
97
- *
98
- * @return {?}
99
- */
100
- function copyArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
82
+ const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');
101
83
  /** @type {?} */
102
- const to = clamp(targetIndex, targetArray.length);
103
- if (currentArray.length) {
104
- targetArray.splice(to, 0, currentArray[currentIndex]);
84
+ const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');
85
+ // If there's no transition for `all` or `transform`, we shouldn't do anything.
86
+ if (!property) {
87
+ return 0;
105
88
  }
89
+ // Get the index of the property that we're interested in and match
90
+ // it up to the same index in `transition-delay` and `transition-duration`.
91
+ /** @type {?} */
92
+ const propertyIndex = transitionedProperties.indexOf(property);
93
+ /** @type {?} */
94
+ const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');
95
+ /** @type {?} */
96
+ const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');
97
+ return parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +
98
+ parseCssTimeUnitsToMs(rawDelays[propertyIndex]);
106
99
  }
107
100
  /**
108
- * Clamps a number between zero and a maximum.
109
- * @param {?} value
110
- * @param {?} max
101
+ * Parses out multiple values from a computed style into an array.
102
+ * @param {?} computedStyle
103
+ * @param {?} name
111
104
  * @return {?}
112
105
  */
113
- function clamp(value, max) {
114
- return Math.max(0, Math.min(max, value));
106
+ function parseCssPropertyValue(computedStyle, name) {
107
+ /** @type {?} */
108
+ const value = computedStyle.getPropertyValue(name);
109
+ return value.split(',').map(part => part.trim());
115
110
  }
116
111
 
117
112
  /**
@@ -119,1395 +114,1667 @@ function clamp(value, max) {
119
114
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
120
115
  */
121
116
  /**
122
- * Event options that can be used to bind an active, capturing event.
117
+ * Options that can be used to bind a passive event listener.
123
118
  * @type {?}
124
119
  */
125
- const activeCapturingEventOptions = normalizePassiveListenerOptions({
126
- passive: false,
127
- capture: true
128
- });
120
+ const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
129
121
  /**
130
- * Service that keeps track of all the drag item and drop container
131
- * instances, and manages global event listeners on the `document`.
122
+ * Options that can be used to bind an active event listener.
123
+ * @type {?}
124
+ */
125
+ const activeEventListenerOptions = normalizePassiveListenerOptions({ passive: false });
126
+ /**
127
+ * Time in milliseconds for which to ignore mouse events, after
128
+ * receiving a touch event. Used to avoid doing double work for
129
+ * touch devices where the browser fires fake mouse events, in
130
+ * addition to touch events.
131
+ * @type {?}
132
+ */
133
+ const MOUSE_EVENT_IGNORE_TIME = 800;
134
+ /**
135
+ * Reference to a draggable item. Used to manipulate or dispose of the item.
132
136
  * \@docs-private
133
- * @template I, C
137
+ * @template T
134
138
  */
135
- // Note: this class is generic, rather than referencing CdkDrag and CdkDropList directly, in order
136
- // to avoid circular imports. If we were to reference them here, importing the registry into the
137
- // classes that are registering themselves will introduce a circular import.
138
- class DragDropRegistry {
139
+ class DragRef {
139
140
  /**
140
- * @param {?} _ngZone
141
+ * @param {?} element
142
+ * @param {?} _config
141
143
  * @param {?} _document
144
+ * @param {?} _ngZone
145
+ * @param {?} _viewportRuler
146
+ * @param {?} _dragDropRegistry
142
147
  */
143
- constructor(_ngZone, _document) {
148
+ constructor(element, _config, _document, _ngZone, _viewportRuler, _dragDropRegistry) {
149
+ this._config = _config;
150
+ this._document = _document;
144
151
  this._ngZone = _ngZone;
152
+ this._viewportRuler = _viewportRuler;
153
+ this._dragDropRegistry = _dragDropRegistry;
145
154
  /**
146
- * Registered drop container instances.
155
+ * CSS `transform` applied to the element when it isn't being dragged. We need a
156
+ * passive transform in order for the dragged element to retain its new position
157
+ * after the user has stopped dragging and because we need to know the relative
158
+ * position in case they start dragging again. This corresponds to `element.style.transform`.
147
159
  */
148
- this._dropInstances = new Set();
160
+ this._passiveTransform = { x: 0, y: 0 };
149
161
  /**
150
- * Registered drag item instances.
162
+ * CSS `transform` that is applied to the element while it's being dragged.
151
163
  */
152
- this._dragInstances = new Set();
164
+ this._activeTransform = { x: 0, y: 0 };
153
165
  /**
154
- * Drag item instances that are currently being dragged.
166
+ * Emits when the item is being moved.
155
167
  */
156
- this._activeDragInstances = new Set();
168
+ this._moveEvents = new Subject();
157
169
  /**
158
- * Keeps track of the event listeners that we've bound to the `document`.
170
+ * Amount of subscriptions to the move event. Used to avoid
171
+ * hitting the zone if the consumer didn't subscribe to it.
159
172
  */
160
- this._globalListeners = new Map();
173
+ this._moveEventSubscriptions = 0;
161
174
  /**
162
- * Emits the `touchmove` or `mousemove` events that are dispatched
163
- * while the user is dragging a drag item instance.
175
+ * Subscription to pointer movement events.
164
176
  */
165
- this.pointerMove = new Subject();
177
+ this._pointerMoveSubscription = Subscription.EMPTY;
166
178
  /**
167
- * Emits the `touchend` or `mouseup` events that are dispatched
168
- * while the user is dragging a drag item instance.
179
+ * Subscription to the event that is dispatched when the user lifts their pointer.
169
180
  */
170
- this.pointerUp = new Subject();
181
+ this._pointerUpSubscription = Subscription.EMPTY;
171
182
  /**
172
- * Event listener that will prevent the default browser action while the user is dragging.
173
- * @param event Event whose default action should be prevented.
183
+ * Cached reference to the boundary element.
174
184
  */
175
- this._preventDefaultWhileDragging = (event) => {
176
- if (this._activeDragInstances.size) {
177
- event.preventDefault();
178
- }
179
- };
180
- this._document = _document;
181
- }
182
- /**
183
- * Adds a drop container to the registry.
184
- * @param {?} drop
185
- * @return {?}
186
- */
187
- registerDropContainer(drop) {
188
- if (!this._dropInstances.has(drop)) {
189
- if (this.getDropContainer(drop.id)) {
190
- throw Error(`Drop instance with id "${drop.id}" has already been registered.`);
191
- }
192
- this._dropInstances.add(drop);
193
- }
194
- }
195
- /**
196
- * Adds a drag item instance to the registry.
197
- * @param {?} drag
198
- * @return {?}
199
- */
200
- registerDragItem(drag) {
201
- this._dragInstances.add(drag);
202
- // The `touchmove` event gets bound once, ahead of time, because WebKit
203
- // won't preventDefault on a dynamically-added `touchmove` listener.
204
- // See https://bugs.webkit.org/show_bug.cgi?id=184250.
205
- if (this._dragInstances.size === 1) {
206
- this._ngZone.runOutsideAngular(() => {
207
- // The event handler has to be explicitly active,
208
- // because newer browsers make it passive by default.
209
- this._document.addEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions);
210
- });
211
- }
212
- }
213
- /**
214
- * Removes a drop container from the registry.
215
- * @param {?} drop
216
- * @return {?}
217
- */
218
- removeDropContainer(drop) {
219
- this._dropInstances.delete(drop);
220
- }
221
- /**
222
- * Removes a drag item instance from the registry.
223
- * @param {?} drag
224
- * @return {?}
225
- */
226
- removeDragItem(drag) {
227
- this._dragInstances.delete(drag);
228
- this.stopDragging(drag);
229
- if (this._dragInstances.size === 0) {
230
- this._document.removeEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions);
231
- }
232
- }
233
- /**
234
- * Starts the dragging sequence for a drag instance.
235
- * @param {?} drag Drag instance which is being dragged.
236
- * @param {?} event Event that initiated the dragging.
237
- * @return {?}
238
- */
239
- startDragging(drag, event) {
240
- this._activeDragInstances.add(drag);
241
- if (this._activeDragInstances.size === 1) {
242
- /** @type {?} */
243
- const isTouchEvent = event.type.startsWith('touch');
244
- /** @type {?} */
245
- const moveEvent = isTouchEvent ? 'touchmove' : 'mousemove';
185
+ this._boundaryElement = null;
186
+ /**
187
+ * Whether the native dragging interactions have been enabled on the root element.
188
+ */
189
+ this._nativeInteractionsEnabled = true;
190
+ /**
191
+ * Elements that can be used to drag the draggable item.
192
+ */
193
+ this._handles = [];
194
+ /**
195
+ * Registered handles that are currently disabled.
196
+ */
197
+ this._disabledHandles = new Set();
198
+ /**
199
+ * Layout direction of the item.
200
+ */
201
+ this._direction = 'ltr';
202
+ this._disabled = false;
203
+ /**
204
+ * Emits as the drag sequence is being prepared.
205
+ */
206
+ this.beforeStarted = new Subject();
207
+ /**
208
+ * Emits when the user starts dragging the item.
209
+ */
210
+ this.started = new Subject();
211
+ /**
212
+ * Emits when the user has released a drag item, before any animations have started.
213
+ */
214
+ this.released = new Subject();
215
+ /**
216
+ * Emits when the user stops dragging an item in the container.
217
+ */
218
+ this.ended = new Subject();
219
+ /**
220
+ * Emits when the user has moved the item into a new container.
221
+ */
222
+ this.entered = new Subject();
223
+ /**
224
+ * Emits when the user removes the item its container by dragging it into another container.
225
+ */
226
+ this.exited = new Subject();
227
+ /**
228
+ * Emits when the user drops the item inside a container.
229
+ */
230
+ this.dropped = new Subject();
231
+ /**
232
+ * Emits as the user is dragging the item. Use with caution,
233
+ * because this event will fire for every pixel that the user has dragged.
234
+ */
235
+ this.moved = new Observable((observer) => {
246
236
  /** @type {?} */
247
- const upEvent = isTouchEvent ? 'touchend' : 'mouseup';
248
- // We explicitly bind __active__ listeners here, because newer browsers will default to
249
- // passive ones for `mousemove` and `touchmove`. The events need to be active, because we
250
- // use `preventDefault` to prevent the page from scrolling while the user is dragging.
251
- this._globalListeners
252
- .set(moveEvent, {
253
- handler: (e) => this.pointerMove.next((/** @type {?} */ (e))),
254
- options: activeCapturingEventOptions
255
- })
256
- .set(upEvent, {
257
- handler: (e) => this.pointerUp.next((/** @type {?} */ (e))),
258
- options: true
259
- })
260
- // Preventing the default action on `mousemove` isn't enough to disable text selection
261
- // on Safari so we need to prevent the selection event as well. Alternatively this can
262
- // be done by setting `user-select: none` on the `body`, however it has causes a style
263
- // recalculation which can be expensive on pages with a lot of elements.
264
- .set('selectstart', {
265
- handler: this._preventDefaultWhileDragging,
266
- options: activeCapturingEventOptions
267
- });
268
- // TODO(crisbeto): prevent mouse wheel scrolling while
269
- // dragging until we've set up proper scroll handling.
270
- if (!isTouchEvent) {
271
- this._globalListeners.set('wheel', {
272
- handler: this._preventDefaultWhileDragging,
273
- options: activeCapturingEventOptions
237
+ const subscription = this._moveEvents.subscribe(observer);
238
+ this._moveEventSubscriptions++;
239
+ return () => {
240
+ subscription.unsubscribe();
241
+ this._moveEventSubscriptions--;
242
+ };
243
+ });
244
+ /**
245
+ * Handler for the `mousedown`/`touchstart` events.
246
+ */
247
+ this._pointerDown = (event) => {
248
+ this.beforeStarted.next();
249
+ // Delegate the event based on whether it started from a handle or the element itself.
250
+ if (this._handles.length) {
251
+ /** @type {?} */
252
+ const targetHandle = this._handles.find(handle => {
253
+ /** @type {?} */
254
+ const target = event.target;
255
+ return !!target && (target === handle || handle.contains((/** @type {?} */ (target))));
274
256
  });
257
+ if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
258
+ this._initializeDragSequence(targetHandle, event);
259
+ }
275
260
  }
276
- this._ngZone.runOutsideAngular(() => {
277
- this._globalListeners.forEach((config, name) => {
278
- this._document.addEventListener(name, config.handler, config.options);
261
+ else if (!this.disabled) {
262
+ this._initializeDragSequence(this._rootElement, event);
263
+ }
264
+ };
265
+ /**
266
+ * Handler that is invoked when the user moves their pointer after they've initiated a drag.
267
+ */
268
+ this._pointerMove = (event) => {
269
+ if (!this._hasStartedDragging) {
270
+ /** @type {?} */
271
+ const pointerPosition = this._getPointerPositionOnPage(event);
272
+ /** @type {?} */
273
+ const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
274
+ /** @type {?} */
275
+ const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
276
+ // Only start dragging after the user has moved more than the minimum distance in either
277
+ // direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
278
+ // in the `pointerMove` subscription, because we're not guaranteed to have one move event
279
+ // per pixel of movement (e.g. if the user moves their pointer quickly).
280
+ if (distanceX + distanceY >= this._config.dragStartThreshold) {
281
+ this._hasStartedDragging = true;
282
+ this._ngZone.run(() => this._startDragSequence(event));
283
+ }
284
+ return;
285
+ }
286
+ // We only need the preview dimensions if we have a boundary element.
287
+ if (this._boundaryElement) {
288
+ // Cache the preview element rect if we haven't cached it already or if
289
+ // we cached it too early before the element dimensions were computed.
290
+ if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) {
291
+ this._previewRect = (this._preview || this._rootElement).getBoundingClientRect();
292
+ }
293
+ }
294
+ /** @type {?} */
295
+ const constrainedPointerPosition = this._getConstrainedPointerPosition(event);
296
+ this._hasMoved = true;
297
+ event.preventDefault();
298
+ this._updatePointerDirectionDelta(constrainedPointerPosition);
299
+ if (this._dropContainer) {
300
+ this._updateActiveDropContainer(constrainedPointerPosition);
301
+ }
302
+ else {
303
+ /** @type {?} */
304
+ const activeTransform = this._activeTransform;
305
+ activeTransform.x =
306
+ constrainedPointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x;
307
+ activeTransform.y =
308
+ constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;
309
+ /** @type {?} */
310
+ const transform = getTransform(activeTransform.x, activeTransform.y);
311
+ // Preserve the previous `transform` value, if there was one. Note that we apply our own
312
+ // transform before the user's, because things like rotation can affect which direction
313
+ // the element will be translated towards.
314
+ this._rootElement.style.transform = this._initialTransform ?
315
+ transform + ' ' + this._initialTransform : transform;
316
+ // Apply transform as attribute if dragging and svg element to work for IE
317
+ if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
318
+ /** @type {?} */
319
+ const appliedTransform = `translate(${activeTransform.x} ${activeTransform.y})`;
320
+ this._rootElement.setAttribute('transform', appliedTransform);
321
+ }
322
+ }
323
+ // Since this event gets fired for every pixel while dragging, we only
324
+ // want to fire it if the consumer opted into it. Also we have to
325
+ // re-enter the zone because we run all of the events on the outside.
326
+ if (this._moveEventSubscriptions > 0) {
327
+ this._ngZone.run(() => {
328
+ this._moveEvents.next({
329
+ source: this,
330
+ pointerPosition: constrainedPointerPosition,
331
+ event,
332
+ delta: this._pointerDirectionDelta
333
+ });
279
334
  });
335
+ }
336
+ };
337
+ /**
338
+ * Handler that is invoked when the user lifts their pointer up, after initiating a drag.
339
+ */
340
+ this._pointerUp = (event) => {
341
+ // Note that here we use `isDragging` from the service, rather than from `this`.
342
+ // The difference is that the one from the service reflects whether a dragging sequence
343
+ // has been initiated, whereas the one on `this` includes whether the user has passed
344
+ // the minimum dragging threshold.
345
+ if (!this._dragDropRegistry.isDragging(this)) {
346
+ return;
347
+ }
348
+ this._removeSubscriptions();
349
+ this._dragDropRegistry.stopDragging(this);
350
+ if (this._handles) {
351
+ this._rootElement.style.webkitTapHighlightColor = this._rootElementTapHighlight;
352
+ }
353
+ if (!this._hasStartedDragging) {
354
+ return;
355
+ }
356
+ this.released.next({ source: this });
357
+ if (!this._dropContainer) {
358
+ // Convert the active transform into a passive one. This means that next time
359
+ // the user starts dragging the item, its position will be calculated relatively
360
+ // to the new passive transform.
361
+ this._passiveTransform.x = this._activeTransform.x;
362
+ this._passiveTransform.y = this._activeTransform.y;
363
+ this._ngZone.run(() => this.ended.next({ source: this }));
364
+ this._dragDropRegistry.stopDragging(this);
365
+ return;
366
+ }
367
+ this._animatePreviewToPlaceholder().then(() => {
368
+ this._cleanupDragArtifacts(event);
369
+ this._dragDropRegistry.stopDragging(this);
280
370
  });
281
- }
371
+ };
372
+ this.withRootElement(element);
373
+ _dragDropRegistry.registerDragItem(this);
282
374
  }
283
375
  /**
284
- * Stops dragging a drag item instance.
285
- * @param {?} drag
376
+ * Whether starting to drag this element is disabled.
286
377
  * @return {?}
287
378
  */
288
- stopDragging(drag) {
289
- this._activeDragInstances.delete(drag);
290
- if (this._activeDragInstances.size === 0) {
291
- this._clearGlobalListeners();
292
- }
379
+ get disabled() {
380
+ return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
293
381
  }
294
382
  /**
295
- * Gets whether a drag item instance is currently being dragged.
296
- * @param {?} drag
383
+ * @param {?} value
297
384
  * @return {?}
298
385
  */
299
- isDragging(drag) {
300
- return this._activeDragInstances.has(drag);
386
+ set disabled(value) {
387
+ /** @type {?} */
388
+ const newValue = coerceBooleanProperty(value);
389
+ if (newValue !== this._disabled) {
390
+ this._disabled = newValue;
391
+ this._toggleNativeDragInteractions();
392
+ }
301
393
  }
302
394
  /**
303
- * Gets a drop container by its id.
304
- * @deprecated No longer being used. To be removed.
305
- * \@breaking-change 8.0.0
306
- * @param {?} id
395
+ * Returns the element that is being used as a placeholder
396
+ * while the current element is being dragged.
307
397
  * @return {?}
308
398
  */
309
- getDropContainer(id) {
310
- return Array.from(this._dropInstances).find(instance => instance.id === id);
399
+ getPlaceholderElement() {
400
+ return this._placeholder;
311
401
  }
312
402
  /**
403
+ * Returns the root draggable element.
313
404
  * @return {?}
314
405
  */
315
- ngOnDestroy() {
316
- this._dragInstances.forEach(instance => this.removeDragItem(instance));
317
- this._dropInstances.forEach(instance => this.removeDropContainer(instance));
318
- this._clearGlobalListeners();
319
- this.pointerMove.complete();
320
- this.pointerUp.complete();
406
+ getRootElement() {
407
+ return this._rootElement;
321
408
  }
322
409
  /**
323
- * Clears out the global event listeners from the `document`.
324
- * @private
325
- * @return {?}
410
+ * Registers the handles that can be used to drag the element.
411
+ * @template THIS
412
+ * @this {THIS}
413
+ * @param {?} handles
414
+ * @return {THIS}
326
415
  */
327
- _clearGlobalListeners() {
328
- this._globalListeners.forEach((config, name) => {
329
- this._document.removeEventListener(name, config.handler, config.options);
330
- });
331
- this._globalListeners.clear();
416
+ withHandles(handles) {
417
+ (/** @type {?} */ (this))._handles = handles.map(handle => coerceElement(handle));
418
+ (/** @type {?} */ (this))._handles.forEach(handle => toggleNativeDragInteractions(handle, false));
419
+ (/** @type {?} */ (this))._toggleNativeDragInteractions();
420
+ return (/** @type {?} */ (this));
332
421
  }
333
- }
334
- DragDropRegistry.decorators = [
335
- { type: Injectable, args: [{ providedIn: 'root' },] },
336
- ];
337
- /** @nocollapse */
338
- DragDropRegistry.ctorParameters = () => [
339
- { type: NgZone },
340
- { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
341
- ];
342
- /** @nocollapse */ DragDropRegistry.ngInjectableDef = defineInjectable({ factory: function DragDropRegistry_Factory() { return new DragDropRegistry(inject(NgZone), inject(DOCUMENT)); }, token: DragDropRegistry, providedIn: "root" });
343
-
344
- /**
345
- * @fileoverview added by tsickle
346
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
347
- */
348
- /**
349
- * Injection token that can be used for a `CdkDrag` to provide itself as a parent to the
350
- * drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily
351
- * to avoid circular imports.
352
- * \@docs-private
353
- * @type {?}
354
- */
355
- const CDK_DRAG_PARENT = new InjectionToken('CDK_DRAG_PARENT');
356
-
357
- /**
358
- * @fileoverview added by tsickle
359
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
360
- */
361
- /**
362
- * Shallow-extends a stylesheet object with another stylesheet object.
363
- * \@docs-private
364
- * @param {?} dest
365
- * @param {?} source
366
- * @return {?}
367
- */
368
- function extendStyles(dest, source) {
369
- for (let key in source) {
370
- if (source.hasOwnProperty(key)) {
371
- dest[(/** @type {?} */ (key))] = source[(/** @type {?} */ (key))];
422
+ /**
423
+ * Registers the template that should be used for the drag preview.
424
+ * @template THIS
425
+ * @this {THIS}
426
+ * @param {?} template Template that from which to stamp out the preview.
427
+ * @return {THIS}
428
+ */
429
+ withPreviewTemplate(template) {
430
+ (/** @type {?} */ (this))._previewTemplate = template;
431
+ return (/** @type {?} */ (this));
432
+ }
433
+ /**
434
+ * Registers the template that should be used for the drag placeholder.
435
+ * @template THIS
436
+ * @this {THIS}
437
+ * @param {?} template Template that from which to stamp out the placeholder.
438
+ * @return {THIS}
439
+ */
440
+ withPlaceholderTemplate(template) {
441
+ (/** @type {?} */ (this))._placeholderTemplate = template;
442
+ return (/** @type {?} */ (this));
443
+ }
444
+ /**
445
+ * Sets an alternate drag root element. The root element is the element that will be moved as
446
+ * the user is dragging. Passing an alternate root element is useful when trying to enable
447
+ * dragging on an element that you might not have access to.
448
+ * @template THIS
449
+ * @this {THIS}
450
+ * @param {?} rootElement
451
+ * @return {THIS}
452
+ */
453
+ withRootElement(rootElement) {
454
+ /** @type {?} */
455
+ const element = coerceElement(rootElement);
456
+ if (element !== (/** @type {?} */ (this))._rootElement) {
457
+ if ((/** @type {?} */ (this))._rootElement) {
458
+ (/** @type {?} */ (this))._removeRootElementListeners((/** @type {?} */ (this))._rootElement);
459
+ }
460
+ element.addEventListener('mousedown', (/** @type {?} */ (this))._pointerDown, activeEventListenerOptions);
461
+ element.addEventListener('touchstart', (/** @type {?} */ (this))._pointerDown, passiveEventListenerOptions);
462
+ (/** @type {?} */ (this))._initialTransform = undefined;
463
+ (/** @type {?} */ (this))._rootElement = element;
372
464
  }
465
+ return (/** @type {?} */ (this));
373
466
  }
374
- return dest;
375
- }
376
- /**
377
- * Toggles whether the native drag interactions should be enabled for an element.
378
- * \@docs-private
379
- * @param {?} element Element on which to toggle the drag interactions.
380
- * @param {?} enable Whether the drag interactions should be enabled.
381
- * @return {?}
382
- */
383
- function toggleNativeDragInteractions(element, enable) {
384
- /** @type {?} */
385
- const userSelect = enable ? '' : 'none';
386
- extendStyles(element.style, {
387
- touchAction: enable ? '' : 'none',
388
- webkitUserDrag: enable ? '' : 'none',
389
- webkitTapHighlightColor: enable ? '' : 'transparent',
390
- userSelect: userSelect,
391
- msUserSelect: userSelect,
392
- webkitUserSelect: userSelect,
393
- MozUserSelect: userSelect
394
- });
395
- }
396
-
397
- /**
398
- * @fileoverview added by tsickle
399
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
400
- */
401
- /**
402
- * Handle that can be used to drag and CdkDrag instance.
403
- */
404
- class CdkDragHandle {
405
467
  /**
406
- * @param {?} element
407
- * @param {?=} parentDrag
468
+ * Element to which the draggable's position will be constrained.
469
+ * @template THIS
470
+ * @this {THIS}
471
+ * @param {?} boundaryElement
472
+ * @return {THIS}
408
473
  */
409
- constructor(element, parentDrag) {
410
- this.element = element;
411
- /**
412
- * Emits when the state of the handle has changed.
413
- */
414
- this._stateChanges = new Subject();
415
- this._disabled = false;
416
- this._parentDrag = parentDrag;
417
- toggleNativeDragInteractions(element.nativeElement, false);
474
+ withBoundaryElement(boundaryElement) {
475
+ (/** @type {?} */ (this))._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
476
+ return (/** @type {?} */ (this));
418
477
  }
419
478
  /**
420
- * Whether starting to drag through this handle is disabled.
479
+ * Removes the dragging functionality from the DOM element.
421
480
  * @return {?}
422
481
  */
423
- get disabled() { return this._disabled; }
482
+ dispose() {
483
+ this._removeRootElementListeners(this._rootElement);
484
+ // Do this check before removing from the registry since it'll
485
+ // stop being considered as dragged once it is removed.
486
+ if (this.isDragging()) {
487
+ // Since we move out the element to the end of the body while it's being
488
+ // dragged, we have to make sure that it's removed if it gets destroyed.
489
+ removeElement(this._rootElement);
490
+ }
491
+ this._destroyPreview();
492
+ this._destroyPlaceholder();
493
+ this._dragDropRegistry.removeDragItem(this);
494
+ this._removeSubscriptions();
495
+ this.beforeStarted.complete();
496
+ this.started.complete();
497
+ this.released.complete();
498
+ this.ended.complete();
499
+ this.entered.complete();
500
+ this.exited.complete();
501
+ this.dropped.complete();
502
+ this._moveEvents.complete();
503
+ this._handles = [];
504
+ this._disabledHandles.clear();
505
+ this._dropContainer = undefined;
506
+ this._boundaryElement = this._rootElement = this._placeholderTemplate =
507
+ this._previewTemplate = this._nextSibling = (/** @type {?} */ (null));
508
+ }
424
509
  /**
425
- * @param {?} value
510
+ * Checks whether the element is currently being dragged.
426
511
  * @return {?}
427
512
  */
428
- set disabled(value) {
429
- this._disabled = coerceBooleanProperty(value);
430
- this._stateChanges.next(this);
513
+ isDragging() {
514
+ return this._hasStartedDragging && this._dragDropRegistry.isDragging(this);
431
515
  }
432
516
  /**
517
+ * Resets a standalone drag item to its initial position.
433
518
  * @return {?}
434
519
  */
435
- ngOnDestroy() {
436
- this._stateChanges.complete();
520
+ reset() {
521
+ this._rootElement.style.transform = this._initialTransform || '';
522
+ this._activeTransform = { x: 0, y: 0 };
523
+ this._passiveTransform = { x: 0, y: 0 };
437
524
  }
438
- }
439
- CdkDragHandle.decorators = [
440
- { type: Directive, args: [{
441
- selector: '[cdkDragHandle]',
442
- host: {
443
- 'class': 'cdk-drag-handle'
444
- }
445
- },] },
446
- ];
447
- /** @nocollapse */
448
- CdkDragHandle.ctorParameters = () => [
449
- { type: ElementRef },
450
- { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_PARENT,] }, { type: Optional }] }
451
- ];
452
- CdkDragHandle.propDecorators = {
453
- disabled: [{ type: Input, args: ['cdkDragHandleDisabled',] }]
454
- };
455
-
456
- /**
457
- * @fileoverview added by tsickle
458
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
459
- */
460
- /**
461
- * Element that will be used as a template for the placeholder of a CdkDrag when
462
- * it is being dragged. The placeholder is displayed in place of the element being dragged.
463
- * @template T
464
- */
465
- class CdkDragPlaceholder {
466
525
  /**
467
- * @param {?} templateRef
526
+ * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.
527
+ * @param {?} handle Handle element that should be disabled.
528
+ * @return {?}
468
529
  */
469
- constructor(templateRef) {
470
- this.templateRef = templateRef;
530
+ disableHandle(handle) {
531
+ if (this._handles.indexOf(handle) > -1) {
532
+ this._disabledHandles.add(handle);
533
+ }
471
534
  }
472
- }
473
- CdkDragPlaceholder.decorators = [
474
- { type: Directive, args: [{
475
- selector: 'ng-template[cdkDragPlaceholder]'
476
- },] },
477
- ];
478
- /** @nocollapse */
479
- CdkDragPlaceholder.ctorParameters = () => [
480
- { type: TemplateRef }
481
- ];
482
- CdkDragPlaceholder.propDecorators = {
483
- data: [{ type: Input }]
484
- };
485
-
486
- /**
487
- * @fileoverview added by tsickle
488
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
489
- */
490
- /**
491
- * Element that will be used as a template for the preview
492
- * of a CdkDrag when it is being dragged.
493
- * @template T
494
- */
495
- class CdkDragPreview {
496
535
  /**
497
- * @param {?} templateRef
536
+ * Enables a handle, if it has been disabled.
537
+ * @param {?} handle Handle element to be enabled.
538
+ * @return {?}
498
539
  */
499
- constructor(templateRef) {
500
- this.templateRef = templateRef;
540
+ enableHandle(handle) {
541
+ this._disabledHandles.delete(handle);
501
542
  }
502
- }
503
- CdkDragPreview.decorators = [
504
- { type: Directive, args: [{
505
- selector: 'ng-template[cdkDragPreview]'
506
- },] },
507
- ];
508
- /** @nocollapse */
509
- CdkDragPreview.ctorParameters = () => [
510
- { type: TemplateRef }
511
- ];
512
- CdkDragPreview.propDecorators = {
513
- data: [{ type: Input }]
514
- };
515
-
516
- /**
517
- * @fileoverview added by tsickle
518
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
519
- */
520
-
521
- /**
522
- * Parses a CSS time value to milliseconds.
523
- * @param {?} value
524
- * @return {?}
525
- */
526
- function parseCssTimeUnitsToMs(value) {
527
- // Some browsers will return it in seconds, whereas others will return milliseconds.
528
- /** @type {?} */
529
- const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;
530
- return parseFloat(value) * multiplier;
531
- }
532
- /**
533
- * Gets the transform transition duration, including the delay, of an element in milliseconds.
534
- * @param {?} element
535
- * @return {?}
536
- */
537
- function getTransformTransitionDurationInMs(element) {
538
- /** @type {?} */
539
- const computedStyle = getComputedStyle(element);
540
- /** @type {?} */
541
- const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');
542
- /** @type {?} */
543
- const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');
544
- // If there's no transition for `all` or `transform`, we shouldn't do anything.
545
- if (!property) {
546
- return 0;
543
+ /**
544
+ * Sets the layout direction of the draggable item.
545
+ * @template THIS
546
+ * @this {THIS}
547
+ * @param {?} direction
548
+ * @return {THIS}
549
+ */
550
+ withDirection(direction) {
551
+ (/** @type {?} */ (this))._direction = direction;
552
+ return (/** @type {?} */ (this));
547
553
  }
548
- // Get the index of the property that we're interested in and match
549
- // it up to the same index in `transition-delay` and `transition-duration`.
550
- /** @type {?} */
551
- const propertyIndex = transitionedProperties.indexOf(property);
552
- /** @type {?} */
553
- const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');
554
- /** @type {?} */
555
- const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');
556
- return parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +
557
- parseCssTimeUnitsToMs(rawDelays[propertyIndex]);
558
- }
559
- /**
560
- * Parses out multiple values from a computed style into an array.
561
- * @param {?} computedStyle
562
- * @param {?} name
563
- * @return {?}
564
- */
565
- function parseCssPropertyValue(computedStyle, name) {
566
- /** @type {?} */
567
- const value = computedStyle.getPropertyValue(name);
568
- return value.split(',').map(part => part.trim());
569
- }
570
-
571
- /**
572
- * @fileoverview added by tsickle
573
- * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
574
- */
575
- /**
576
- * Options that can be used to bind a passive event listener.
577
- * @type {?}
578
- */
579
- const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
580
- /**
581
- * Options that can be used to bind an active event listener.
582
- * @type {?}
583
- */
584
- const activeEventListenerOptions = normalizePassiveListenerOptions({ passive: false });
585
- /**
586
- * Time in milliseconds for which to ignore mouse events, after
587
- * receiving a touch event. Used to avoid doing double work for
588
- * touch devices where the browser fires fake mouse events, in
589
- * addition to touch events.
590
- * @type {?}
591
- */
592
- const MOUSE_EVENT_IGNORE_TIME = 800;
593
- /**
594
- * Reference to a draggable item. Used to manipulate or dispose of the item.
595
- * \@docs-private
596
- * @template T
597
- */
598
- class DragRef {
599
554
  /**
600
- * @param {?} element
601
- * @param {?} _document
602
- * @param {?} _ngZone
603
- * @param {?} _viewContainerRef
604
- * @param {?} _viewportRuler
605
- * @param {?} _dragDropRegistry
606
- * @param {?} _config
607
- * @param {?=} dropContainer
608
- * @param {?=} _dir
555
+ * Sets the container that the item is part of.
556
+ * @param {?} container
557
+ * @return {?}
609
558
  */
610
- constructor(element, _document, _ngZone, _viewContainerRef, _viewportRuler, _dragDropRegistry, _config, dropContainer, _dir) {
611
- this._document = _document;
612
- this._ngZone = _ngZone;
613
- this._viewContainerRef = _viewContainerRef;
614
- this._viewportRuler = _viewportRuler;
615
- this._dragDropRegistry = _dragDropRegistry;
616
- this._config = _config;
617
- this.dropContainer = dropContainer;
618
- this._dir = _dir;
619
- /**
620
- * CSS `transform` applied to the element when it isn't being dragged. We need a
621
- * passive transform in order for the dragged element to retain its new position
622
- * after the user has stopped dragging and because we need to know the relative
623
- * position in case they start dragging again. This corresponds to `element.style.transform`.
624
- */
625
- this._passiveTransform = { x: 0, y: 0 };
626
- /**
627
- * CSS `transform` that is applied to the element while it's being dragged.
628
- */
629
- this._activeTransform = { x: 0, y: 0 };
630
- /**
631
- * Emits when the item is being moved.
632
- */
633
- this._moveEvents = new Subject();
634
- /**
635
- * Amount of subscriptions to the move event. Used to avoid
636
- * hitting the zone if the consumer didn't subscribe to it.
637
- */
638
- this._moveEventSubscriptions = 0;
639
- /**
640
- * Subscription to pointer movement events.
641
- */
642
- this._pointerMoveSubscription = Subscription.EMPTY;
643
- /**
644
- * Subscription to the event that is dispatched when the user lifts their pointer.
645
- */
646
- this._pointerUpSubscription = Subscription.EMPTY;
647
- /**
648
- * Cached reference to the boundary element.
649
- */
650
- this._boundaryElement = null;
651
- /**
652
- * Whether the native dragging interactions have been enabled on the root element.
653
- */
654
- this._nativeInteractionsEnabled = true;
655
- /**
656
- * Elements that can be used to drag the draggable item.
657
- */
658
- this._handles = [];
659
- /**
660
- * Registered handles that are currently disabled.
661
- */
662
- this._disabledHandles = new Set();
663
- this._disabled = false;
664
- /**
665
- * Emits as the drag sequence is being prepared.
666
- */
667
- this.beforeStarted = new Subject();
668
- /**
669
- * Emits when the user starts dragging the item.
670
- */
671
- this.started = new Subject();
672
- /**
673
- * Emits when the user has released a drag item, before any animations have started.
674
- */
675
- this.released = new Subject();
676
- /**
677
- * Emits when the user stops dragging an item in the container.
678
- */
679
- this.ended = new Subject();
680
- /**
681
- * Emits when the user has moved the item into a new container.
682
- */
683
- this.entered = new Subject();
684
- /**
685
- * Emits when the user removes the item its container by dragging it into another container.
686
- */
687
- this.exited = new Subject();
688
- /**
689
- * Emits when the user drops the item inside a container.
690
- */
691
- this.dropped = new Subject();
692
- /**
693
- * Emits as the user is dragging the item. Use with caution,
694
- * because this event will fire for every pixel that the user has dragged.
695
- */
696
- this.moved = new Observable((observer) => {
697
- /** @type {?} */
698
- const subscription = this._moveEvents.subscribe(observer);
699
- this._moveEventSubscriptions++;
700
- return () => {
701
- subscription.unsubscribe();
702
- this._moveEventSubscriptions--;
703
- };
704
- });
705
- /**
706
- * Handler for the `mousedown`/`touchstart` events.
707
- */
708
- this._pointerDown = (event) => {
709
- this.beforeStarted.next();
710
- // Delegate the event based on whether it started from a handle or the element itself.
711
- if (this._handles.length) {
712
- /** @type {?} */
713
- const targetHandle = this._handles.find(handle => {
714
- /** @type {?} */
715
- const target = event.target;
716
- return !!target && (target === handle || handle.contains((/** @type {?} */ (target))));
717
- });
718
- if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
719
- this._initializeDragSequence(targetHandle, event);
720
- }
721
- }
722
- else if (!this.disabled) {
723
- this._initializeDragSequence(this._rootElement, event);
724
- }
725
- };
726
- /**
727
- * Handler that is invoked when the user moves their pointer after they've initiated a drag.
728
- */
729
- this._pointerMove = (event) => {
730
- if (!this._hasStartedDragging) {
731
- /** @type {?} */
732
- const pointerPosition = this._getPointerPositionOnPage(event);
733
- /** @type {?} */
734
- const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
735
- /** @type {?} */
736
- const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
737
- // Only start dragging after the user has moved more than the minimum distance in either
738
- // direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
739
- // in the `pointerMove` subscription, because we're not guaranteed to have one move event
740
- // per pixel of movement (e.g. if the user moves their pointer quickly).
741
- if (distanceX + distanceY >= this._config.dragStartThreshold) {
742
- this._hasStartedDragging = true;
743
- this._ngZone.run(() => this._startDragSequence(event));
744
- }
745
- return;
746
- }
747
- // We only need the preview dimensions if we have a boundary element.
748
- if (this._boundaryElement) {
749
- // Cache the preview element rect if we haven't cached it already or if
750
- // we cached it too early before the element dimensions were computed.
751
- if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) {
752
- this._previewRect = (this._preview || this._rootElement).getBoundingClientRect();
753
- }
754
- }
559
+ _withDropContainer(container) {
560
+ this._dropContainer = container;
561
+ }
562
+ /**
563
+ * Unsubscribes from the global subscriptions.
564
+ * @private
565
+ * @return {?}
566
+ */
567
+ _removeSubscriptions() {
568
+ this._pointerMoveSubscription.unsubscribe();
569
+ this._pointerUpSubscription.unsubscribe();
570
+ }
571
+ /**
572
+ * Destroys the preview element and its ViewRef.
573
+ * @private
574
+ * @return {?}
575
+ */
576
+ _destroyPreview() {
577
+ if (this._preview) {
578
+ removeElement(this._preview);
579
+ }
580
+ if (this._previewRef) {
581
+ this._previewRef.destroy();
582
+ }
583
+ this._preview = this._previewRef = (/** @type {?} */ (null));
584
+ }
585
+ /**
586
+ * Destroys the placeholder element and its ViewRef.
587
+ * @private
588
+ * @return {?}
589
+ */
590
+ _destroyPlaceholder() {
591
+ if (this._placeholder) {
592
+ removeElement(this._placeholder);
593
+ }
594
+ if (this._placeholderRef) {
595
+ this._placeholderRef.destroy();
596
+ }
597
+ this._placeholder = this._placeholderRef = (/** @type {?} */ (null));
598
+ }
599
+ /**
600
+ * Starts the dragging sequence.
601
+ * @private
602
+ * @param {?} event
603
+ * @return {?}
604
+ */
605
+ _startDragSequence(event) {
606
+ // Emit the event on the item before the one on the container.
607
+ this.started.next({ source: this });
608
+ if (isTouchEvent(event)) {
609
+ this._lastTouchEventTime = Date.now();
610
+ }
611
+ if (this._dropContainer) {
755
612
  /** @type {?} */
756
- const constrainedPointerPosition = this._getConstrainedPointerPosition(event);
757
- this._hasMoved = true;
613
+ const element = this._rootElement;
614
+ // Grab the `nextSibling` before the preview and placeholder
615
+ // have been created so we don't get the preview by accident.
616
+ this._nextSibling = element.nextSibling;
617
+ /** @type {?} */
618
+ const preview = this._preview = this._createPreviewElement();
619
+ /** @type {?} */
620
+ const placeholder = this._placeholder = this._createPlaceholderElement();
621
+ // We move the element out at the end of the body and we make it hidden, because keeping it in
622
+ // place will throw off the consumer's `:last-child` selectors. We can't remove the element
623
+ // from the DOM completely, because iOS will stop firing all subsequent events in the chain.
624
+ element.style.display = 'none';
625
+ this._document.body.appendChild((/** @type {?} */ (element.parentNode)).replaceChild(placeholder, element));
626
+ this._document.body.appendChild(preview);
627
+ this._dropContainer.start();
628
+ }
629
+ }
630
+ /**
631
+ * Sets up the different variables and subscriptions
632
+ * that will be necessary for the dragging sequence.
633
+ * @private
634
+ * @param {?} referenceElement Element that started the drag sequence.
635
+ * @param {?} event Browser event object that started the sequence.
636
+ * @return {?}
637
+ */
638
+ _initializeDragSequence(referenceElement, event) {
639
+ // Always stop propagation for the event that initializes
640
+ // the dragging sequence, in order to prevent it from potentially
641
+ // starting another sequence for a draggable parent somewhere up the DOM tree.
642
+ event.stopPropagation();
643
+ /** @type {?} */
644
+ const isDragging = this.isDragging();
645
+ /** @type {?} */
646
+ const isTouchSequence = isTouchEvent(event);
647
+ /** @type {?} */
648
+ const isAuxiliaryMouseButton = !isTouchSequence && ((/** @type {?} */ (event))).button !== 0;
649
+ /** @type {?} */
650
+ const rootElement = this._rootElement;
651
+ /** @type {?} */
652
+ const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime &&
653
+ this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
654
+ // If the event started from an element with the native HTML drag&drop, it'll interfere
655
+ // with our own dragging (e.g. `img` tags do it by default). Prevent the default action
656
+ // to stop it from happening. Note that preventing on `dragstart` also seems to work, but
657
+ // it's flaky and it fails if the user drags it away quickly. Also note that we only want
658
+ // to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
659
+ // events from firing on touch devices.
660
+ if (event.target && ((/** @type {?} */ (event.target))).draggable && event.type === 'mousedown') {
758
661
  event.preventDefault();
759
- this._updatePointerDirectionDelta(constrainedPointerPosition);
760
- if (this.dropContainer) {
761
- this._updateActiveDropContainer(constrainedPointerPosition);
762
- }
763
- else {
662
+ }
663
+ // Abort if the user is already dragging or is using a mouse button other than the primary one.
664
+ if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) {
665
+ return;
666
+ }
667
+ // Cache the previous transform amount only after the first drag sequence, because
668
+ // we don't want our own transforms to stack on top of each other.
669
+ if (this._initialTransform == null) {
670
+ this._initialTransform = this._rootElement.style.transform || '';
671
+ }
672
+ // If we've got handles, we need to disable the tap highlight on the entire root element,
673
+ // otherwise iOS will still add it, even though all the drag interactions on the handle
674
+ // are disabled.
675
+ if (this._handles.length) {
676
+ this._rootElementTapHighlight = rootElement.style.webkitTapHighlightColor;
677
+ rootElement.style.webkitTapHighlightColor = 'transparent';
678
+ }
679
+ this._toggleNativeDragInteractions();
680
+ this._hasStartedDragging = this._hasMoved = false;
681
+ this._initialContainer = (/** @type {?} */ (this._dropContainer));
682
+ this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
683
+ this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
684
+ this._scrollPosition = this._viewportRuler.getViewportScrollPosition();
685
+ if (this._boundaryElement) {
686
+ this._boundaryRect = this._boundaryElement.getBoundingClientRect();
687
+ }
688
+ // If we have a custom preview template, the element won't be visible anyway so we avoid the
689
+ // extra `getBoundingClientRect` calls and just move the preview next to the cursor.
690
+ this._pickupPositionInElement = this._previewTemplate && this._previewTemplate.template ?
691
+ { x: 0, y: 0 } :
692
+ this._getPointerPositionInElement(referenceElement, event);
693
+ /** @type {?} */
694
+ const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
695
+ this._pointerDirectionDelta = { x: 0, y: 0 };
696
+ this._pointerPositionAtLastDirectionChange = { x: pointerPosition.x, y: pointerPosition.y };
697
+ this._dragDropRegistry.startDragging(this, event);
698
+ }
699
+ /**
700
+ * Cleans up the DOM artifacts that were added to facilitate the element being dragged.
701
+ * @private
702
+ * @param {?} event
703
+ * @return {?}
704
+ */
705
+ _cleanupDragArtifacts(event) {
706
+ // Restore the element's visibility and insert it at its old position in the DOM.
707
+ // It's important that we maintain the position, because moving the element around in the DOM
708
+ // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
709
+ // while moving the existing elements in all other cases.
710
+ this._rootElement.style.display = '';
711
+ if (this._nextSibling) {
712
+ (/** @type {?} */ (this._nextSibling.parentNode)).insertBefore(this._rootElement, this._nextSibling);
713
+ }
714
+ else {
715
+ this._initialContainer.element.appendChild(this._rootElement);
716
+ }
717
+ this._destroyPreview();
718
+ this._destroyPlaceholder();
719
+ this._boundaryRect = this._previewRect = undefined;
720
+ // Re-enter the NgZone since we bound `document` events on the outside.
721
+ this._ngZone.run(() => {
722
+ /** @type {?} */
723
+ const container = (/** @type {?} */ (this._dropContainer));
724
+ /** @type {?} */
725
+ const currentIndex = container.getItemIndex(this);
726
+ const { x, y } = this._getPointerPositionOnPage(event);
727
+ /** @type {?} */
728
+ const isPointerOverContainer = container._isOverContainer(x, y);
729
+ this.ended.next({ source: this });
730
+ this.dropped.next({
731
+ item: this,
732
+ currentIndex,
733
+ previousIndex: this._initialContainer.getItemIndex(this),
734
+ container: container,
735
+ previousContainer: this._initialContainer,
736
+ isPointerOverContainer
737
+ });
738
+ container.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
739
+ this._dropContainer = this._initialContainer;
740
+ });
741
+ }
742
+ /**
743
+ * Updates the item's position in its drop container, or moves it
744
+ * into a new one, depending on its current drag position.
745
+ * @private
746
+ * @param {?} __0
747
+ * @return {?}
748
+ */
749
+ _updateActiveDropContainer({ x, y }) {
750
+ // Drop container that draggable has been moved into.
751
+ /** @type {?} */
752
+ let newContainer = (/** @type {?} */ (this._dropContainer))._getSiblingContainerFromPosition(this, x, y) ||
753
+ this._initialContainer._getSiblingContainerFromPosition(this, x, y);
754
+ // If we couldn't find a new container to move the item into, and the item has left it's
755
+ // initial container, check whether the it's over the initial container. This handles the
756
+ // case where two containers are connected one way and the user tries to undo dragging an
757
+ // item into a new container.
758
+ if (!newContainer && this._dropContainer !== this._initialContainer &&
759
+ this._initialContainer._isOverContainer(x, y)) {
760
+ newContainer = this._initialContainer;
761
+ }
762
+ if (newContainer && newContainer !== this._dropContainer) {
763
+ this._ngZone.run(() => {
764
+ // Notify the old container that the item has left.
765
+ this.exited.next({ item: this, container: (/** @type {?} */ (this._dropContainer)) });
766
+ (/** @type {?} */ (this._dropContainer)).exit(this);
767
+ // Notify the new container that the item has entered.
768
+ this.entered.next({ item: this, container: (/** @type {?} */ (newContainer)) });
769
+ this._dropContainer = (/** @type {?} */ (newContainer));
770
+ this._dropContainer.enter(this, x, y);
771
+ });
772
+ }
773
+ (/** @type {?} */ (this._dropContainer))._sortItem(this, x, y, this._pointerDirectionDelta);
774
+ this._preview.style.transform =
775
+ getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
776
+ }
777
+ /**
778
+ * Creates the element that will be rendered next to the user's pointer
779
+ * and will be used as a preview of the element that is being dragged.
780
+ * @private
781
+ * @return {?}
782
+ */
783
+ _createPreviewElement() {
784
+ /** @type {?} */
785
+ const previewConfig = this._previewTemplate;
786
+ /** @type {?} */
787
+ const previewTemplate = previewConfig ? previewConfig.template : null;
788
+ /** @type {?} */
789
+ let preview;
790
+ if (previewTemplate) {
791
+ /** @type {?} */
792
+ const viewRef = (/** @type {?} */ (previewConfig)).viewContainer.createEmbeddedView(previewTemplate, (/** @type {?} */ (previewConfig)).context);
793
+ preview = viewRef.rootNodes[0];
794
+ this._previewRef = viewRef;
795
+ preview.style.transform =
796
+ getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
797
+ }
798
+ else {
799
+ /** @type {?} */
800
+ const element = this._rootElement;
801
+ /** @type {?} */
802
+ const elementRect = element.getBoundingClientRect();
803
+ preview = deepCloneNode(element);
804
+ preview.style.width = `${elementRect.width}px`;
805
+ preview.style.height = `${elementRect.height}px`;
806
+ preview.style.transform = getTransform(elementRect.left, elementRect.top);
807
+ }
808
+ extendStyles(preview.style, {
809
+ // It's important that we disable the pointer events on the preview, because
810
+ // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
811
+ pointerEvents: 'none',
812
+ position: 'fixed',
813
+ top: '0',
814
+ left: '0',
815
+ zIndex: '1000'
816
+ });
817
+ toggleNativeDragInteractions(preview, false);
818
+ preview.classList.add('cdk-drag-preview');
819
+ preview.setAttribute('dir', this._direction);
820
+ return preview;
821
+ }
822
+ /**
823
+ * Animates the preview element from its current position to the location of the drop placeholder.
824
+ * @private
825
+ * @return {?} Promise that resolves when the animation completes.
826
+ */
827
+ _animatePreviewToPlaceholder() {
828
+ // If the user hasn't moved yet, the transitionend event won't fire.
829
+ if (!this._hasMoved) {
830
+ return Promise.resolve();
831
+ }
832
+ /** @type {?} */
833
+ const placeholderRect = this._placeholder.getBoundingClientRect();
834
+ // Apply the class that adds a transition to the preview.
835
+ this._preview.classList.add('cdk-drag-animating');
836
+ // Move the preview to the placeholder position.
837
+ this._preview.style.transform = getTransform(placeholderRect.left, placeholderRect.top);
838
+ // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since
839
+ // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
840
+ // apply its style, we take advantage of the available info to figure out whether we need to
841
+ // bind the event in the first place.
842
+ /** @type {?} */
843
+ const duration = getTransformTransitionDurationInMs(this._preview);
844
+ if (duration === 0) {
845
+ return Promise.resolve();
846
+ }
847
+ return this._ngZone.runOutsideAngular(() => {
848
+ return new Promise(resolve => {
764
849
  /** @type {?} */
765
- const activeTransform = this._activeTransform;
766
- activeTransform.x =
767
- constrainedPointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x;
768
- activeTransform.y =
769
- constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;
850
+ const handler = (/** @type {?} */ (((event) => {
851
+ if (!event || (event.target === this._preview && event.propertyName === 'transform')) {
852
+ this._preview.removeEventListener('transitionend', handler);
853
+ resolve();
854
+ clearTimeout(timeout);
855
+ }
856
+ })));
857
+ // If a transition is short enough, the browser might not fire the `transitionend` event.
858
+ // Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
859
+ // fire if the transition hasn't completed when it was supposed to.
770
860
  /** @type {?} */
771
- const transform = getTransform(activeTransform.x, activeTransform.y);
772
- // Preserve the previous `transform` value, if there was one. Note that we apply our own
773
- // transform before the user's, because things like rotation can affect which direction
774
- // the element will be translated towards.
775
- this._rootElement.style.transform = this._initialTransform ?
776
- transform + ' ' + this._initialTransform : transform;
777
- // Apply transform as attribute if dragging and svg element to work for IE
778
- if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
779
- /** @type {?} */
780
- const appliedTransform = `translate(${activeTransform.x} ${activeTransform.y})`;
781
- this._rootElement.setAttribute('transform', appliedTransform);
782
- }
783
- }
784
- // Since this event gets fired for every pixel while dragging, we only
785
- // want to fire it if the consumer opted into it. Also we have to
786
- // re-enter the zone because we run all of the events on the outside.
787
- if (this._moveEventSubscriptions > 0) {
788
- this._ngZone.run(() => {
789
- this._moveEvents.next({
790
- source: this,
791
- pointerPosition: constrainedPointerPosition,
792
- event,
793
- delta: this._pointerDirectionDelta
794
- });
795
- });
796
- }
797
- };
798
- /**
799
- * Handler that is invoked when the user lifts their pointer up, after initiating a drag.
800
- */
801
- this._pointerUp = (event) => {
802
- // Note that here we use `isDragging` from the service, rather than from `this`.
803
- // The difference is that the one from the service reflects whether a dragging sequence
804
- // has been initiated, whereas the one on `this` includes whether the user has passed
805
- // the minimum dragging threshold.
806
- if (!this._dragDropRegistry.isDragging(this)) {
807
- return;
808
- }
809
- this._removeSubscriptions();
810
- this._dragDropRegistry.stopDragging(this);
811
- if (this._handles) {
812
- this._rootElement.style.webkitTapHighlightColor = this._rootElementTapHighlight;
813
- }
814
- if (!this._hasStartedDragging) {
815
- return;
816
- }
817
- this.released.next({ source: this });
818
- if (!this.dropContainer) {
819
- // Convert the active transform into a passive one. This means that next time
820
- // the user starts dragging the item, its position will be calculated relatively
821
- // to the new passive transform.
822
- this._passiveTransform.x = this._activeTransform.x;
823
- this._passiveTransform.y = this._activeTransform.y;
824
- this._ngZone.run(() => this.ended.next({ source: this }));
825
- this._dragDropRegistry.stopDragging(this);
826
- return;
827
- }
828
- this._animatePreviewToPlaceholder().then(() => {
829
- this._cleanupDragArtifacts(event);
830
- this._dragDropRegistry.stopDragging(this);
861
+ const timeout = setTimeout((/** @type {?} */ (handler)), duration * 1.5);
862
+ this._preview.addEventListener('transitionend', handler);
831
863
  });
864
+ });
865
+ }
866
+ /**
867
+ * Creates an element that will be shown instead of the current element while dragging.
868
+ * @private
869
+ * @return {?}
870
+ */
871
+ _createPlaceholderElement() {
872
+ /** @type {?} */
873
+ const placeholderConfig = this._placeholderTemplate;
874
+ /** @type {?} */
875
+ const placeholderTemplate = placeholderConfig ? placeholderConfig.template : null;
876
+ /** @type {?} */
877
+ let placeholder;
878
+ if (placeholderTemplate) {
879
+ this._placeholderRef = (/** @type {?} */ (placeholderConfig)).viewContainer.createEmbeddedView(placeholderTemplate, (/** @type {?} */ (placeholderConfig)).context);
880
+ placeholder = this._placeholderRef.rootNodes[0];
881
+ }
882
+ else {
883
+ placeholder = deepCloneNode(this._rootElement);
884
+ }
885
+ placeholder.classList.add('cdk-drag-placeholder');
886
+ return placeholder;
887
+ }
888
+ /**
889
+ * Figures out the coordinates at which an element was picked up.
890
+ * @private
891
+ * @param {?} referenceElement Element that initiated the dragging.
892
+ * @param {?} event Event that initiated the dragging.
893
+ * @return {?}
894
+ */
895
+ _getPointerPositionInElement(referenceElement, event) {
896
+ /** @type {?} */
897
+ const elementRect = this._rootElement.getBoundingClientRect();
898
+ /** @type {?} */
899
+ const handleElement = referenceElement === this._rootElement ? null : referenceElement;
900
+ /** @type {?} */
901
+ const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect;
902
+ /** @type {?} */
903
+ const point = isTouchEvent(event) ? event.targetTouches[0] : event;
904
+ /** @type {?} */
905
+ const x = point.pageX - referenceRect.left - this._scrollPosition.left;
906
+ /** @type {?} */
907
+ const y = point.pageY - referenceRect.top - this._scrollPosition.top;
908
+ return {
909
+ x: referenceRect.left - elementRect.left + x,
910
+ y: referenceRect.top - elementRect.top + y
832
911
  };
833
- this.withRootElement(element);
834
- _dragDropRegistry.registerDragItem(this);
835
912
  }
836
913
  /**
837
- * Whether starting to drag this element is disabled.
914
+ * Determines the point of the page that was touched by the user.
915
+ * @private
916
+ * @param {?} event
917
+ * @return {?}
918
+ */
919
+ _getPointerPositionOnPage(event) {
920
+ // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
921
+ /** @type {?} */
922
+ const point = isTouchEvent(event) ? (event.touches[0] || event.changedTouches[0]) : event;
923
+ return {
924
+ x: point.pageX - this._scrollPosition.left,
925
+ y: point.pageY - this._scrollPosition.top
926
+ };
927
+ }
928
+ /**
929
+ * Gets the pointer position on the page, accounting for any position constraints.
930
+ * @private
931
+ * @param {?} event
932
+ * @return {?}
933
+ */
934
+ _getConstrainedPointerPosition(event) {
935
+ /** @type {?} */
936
+ const point = this._getPointerPositionOnPage(event);
937
+ /** @type {?} */
938
+ const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
939
+ if (this.lockAxis === 'x' || dropContainerLock === 'x') {
940
+ point.y = this._pickupPositionOnPage.y;
941
+ }
942
+ else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
943
+ point.x = this._pickupPositionOnPage.x;
944
+ }
945
+ if (this._boundaryRect) {
946
+ const { x: pickupX, y: pickupY } = this._pickupPositionInElement;
947
+ /** @type {?} */
948
+ const boundaryRect = this._boundaryRect;
949
+ /** @type {?} */
950
+ const previewRect = (/** @type {?} */ (this._previewRect));
951
+ /** @type {?} */
952
+ const minY = boundaryRect.top + pickupY;
953
+ /** @type {?} */
954
+ const maxY = boundaryRect.bottom - (previewRect.height - pickupY);
955
+ /** @type {?} */
956
+ const minX = boundaryRect.left + pickupX;
957
+ /** @type {?} */
958
+ const maxX = boundaryRect.right - (previewRect.width - pickupX);
959
+ point.x = clamp(point.x, minX, maxX);
960
+ point.y = clamp(point.y, minY, maxY);
961
+ }
962
+ return point;
963
+ }
964
+ /**
965
+ * Updates the current drag delta, based on the user's current pointer position on the page.
966
+ * @private
967
+ * @param {?} pointerPositionOnPage
838
968
  * @return {?}
839
969
  */
840
- get disabled() {
841
- return this._disabled || !!(this.dropContainer && this.dropContainer.disabled);
970
+ _updatePointerDirectionDelta(pointerPositionOnPage) {
971
+ const { x, y } = pointerPositionOnPage;
972
+ /** @type {?} */
973
+ const delta = this._pointerDirectionDelta;
974
+ /** @type {?} */
975
+ const positionSinceLastChange = this._pointerPositionAtLastDirectionChange;
976
+ // Amount of pixels the user has dragged since the last time the direction changed.
977
+ /** @type {?} */
978
+ const changeX = Math.abs(x - positionSinceLastChange.x);
979
+ /** @type {?} */
980
+ const changeY = Math.abs(y - positionSinceLastChange.y);
981
+ // Because we handle pointer events on a per-pixel basis, we don't want the delta
982
+ // to change for every pixel, otherwise anything that depends on it can look erratic.
983
+ // To make the delta more consistent, we track how much the user has moved since the last
984
+ // delta change and we only update it after it has reached a certain threshold.
985
+ if (changeX > this._config.pointerDirectionChangeThreshold) {
986
+ delta.x = x > positionSinceLastChange.x ? 1 : -1;
987
+ positionSinceLastChange.x = x;
988
+ }
989
+ if (changeY > this._config.pointerDirectionChangeThreshold) {
990
+ delta.y = y > positionSinceLastChange.y ? 1 : -1;
991
+ positionSinceLastChange.y = y;
992
+ }
993
+ return delta;
842
994
  }
843
995
  /**
844
- * @param {?} value
996
+ * Toggles the native drag interactions, based on how many handles are registered.
997
+ * @private
845
998
  * @return {?}
846
999
  */
847
- set disabled(value) {
1000
+ _toggleNativeDragInteractions() {
1001
+ if (!this._rootElement || !this._handles) {
1002
+ return;
1003
+ }
848
1004
  /** @type {?} */
849
- const newValue = coerceBooleanProperty(value);
850
- if (newValue !== this._disabled) {
851
- this._disabled = newValue;
852
- this._toggleNativeDragInteractions();
1005
+ const shouldEnable = this.disabled || this._handles.length > 0;
1006
+ if (shouldEnable !== this._nativeInteractionsEnabled) {
1007
+ this._nativeInteractionsEnabled = shouldEnable;
1008
+ toggleNativeDragInteractions(this._rootElement, shouldEnable);
853
1009
  }
854
1010
  }
855
1011
  /**
856
- * Returns the element that is being used as a placeholder
857
- * while the current element is being dragged.
1012
+ * Removes the manually-added event listeners from the root element.
1013
+ * @private
1014
+ * @param {?} element
858
1015
  * @return {?}
859
1016
  */
860
- getPlaceholderElement() {
861
- return this._placeholder;
1017
+ _removeRootElementListeners(element) {
1018
+ element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
1019
+ element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
862
1020
  }
863
- /**
864
- * Returns the root draggable element.
865
- * @return {?}
866
- */
867
- getRootElement() {
868
- return this._rootElement;
1021
+ }
1022
+ /**
1023
+ * Gets a 3d `transform` that can be applied to an element.
1024
+ * @param {?} x Desired position of the element along the X axis.
1025
+ * @param {?} y Desired position of the element along the Y axis.
1026
+ * @return {?}
1027
+ */
1028
+ function getTransform(x, y) {
1029
+ // Round the transforms since some browsers will
1030
+ // blur the elements for sub-pixel transforms.
1031
+ return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
1032
+ }
1033
+ /**
1034
+ * Creates a deep clone of an element.
1035
+ * @param {?} node
1036
+ * @return {?}
1037
+ */
1038
+ function deepCloneNode(node) {
1039
+ /** @type {?} */
1040
+ const clone = (/** @type {?} */ (node.cloneNode(true)));
1041
+ // Remove the `id` to avoid having multiple elements with the same id on the page.
1042
+ clone.removeAttribute('id');
1043
+ return clone;
1044
+ }
1045
+ /**
1046
+ * Clamps a value between a minimum and a maximum.
1047
+ * @param {?} value
1048
+ * @param {?} min
1049
+ * @param {?} max
1050
+ * @return {?}
1051
+ */
1052
+ function clamp(value, min, max) {
1053
+ return Math.max(min, Math.min(max, value));
1054
+ }
1055
+ /**
1056
+ * Helper to remove an element from the DOM and to do all the necessary null checks.
1057
+ * @param {?} element Element to be removed.
1058
+ * @return {?}
1059
+ */
1060
+ function removeElement(element) {
1061
+ if (element && element.parentNode) {
1062
+ element.parentNode.removeChild(element);
869
1063
  }
870
- /**
871
- * Registers the handles that can be used to drag the element.
872
- * @template THIS
873
- * @this {THIS}
874
- * @param {?} handles
875
- * @return {THIS}
876
- */
877
- withHandles(handles) {
878
- (/** @type {?} */ (this))._handles = handles.map(handle => coerceElement(handle));
879
- (/** @type {?} */ (this))._handles.forEach(handle => toggleNativeDragInteractions(handle, false));
880
- (/** @type {?} */ (this))._toggleNativeDragInteractions();
881
- return (/** @type {?} */ (this));
1064
+ }
1065
+ /**
1066
+ * Determines whether an event is a touch event.
1067
+ * @param {?} event
1068
+ * @return {?}
1069
+ */
1070
+ function isTouchEvent(event) {
1071
+ return event.type.startsWith('touch');
1072
+ }
1073
+
1074
+ /**
1075
+ * @fileoverview added by tsickle
1076
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
1077
+ */
1078
+
1079
+ /**
1080
+ * Moves an item one index in an array to another.
1081
+ * @template T
1082
+ * @param {?} array Array in which to move the item.
1083
+ * @param {?} fromIndex Starting index of the item.
1084
+ * @param {?} toIndex Index to which the item should be moved.
1085
+ * @return {?}
1086
+ */
1087
+ function moveItemInArray(array, fromIndex, toIndex) {
1088
+ /** @type {?} */
1089
+ const from = clamp$1(fromIndex, array.length - 1);
1090
+ /** @type {?} */
1091
+ const to = clamp$1(toIndex, array.length - 1);
1092
+ if (from === to) {
1093
+ return;
882
1094
  }
883
- /**
884
- * Registers the template that should be used for the drag preview.
885
- * @template THIS
886
- * @this {THIS}
887
- * @param {?} template Template that from which to stamp out the preview.
888
- * @param {?=} context Variables to add to the template's context.
889
- * @return {THIS}
890
- */
891
- withPreviewTemplate(template, context) {
892
- (/** @type {?} */ (this))._previewTemplate = { template, context };
893
- return (/** @type {?} */ (this));
1095
+ /** @type {?} */
1096
+ const target = array[from];
1097
+ /** @type {?} */
1098
+ const delta = to < from ? -1 : 1;
1099
+ for (let i = from; i !== to; i += delta) {
1100
+ array[i] = array[i + delta];
894
1101
  }
895
- /**
896
- * Registers the template that should be used for the drag placeholder.
897
- * @template THIS
898
- * @this {THIS}
899
- * @param {?} template Template that from which to stamp out the placeholder.
900
- * @param {?=} context Variables to add to the template's context.
901
- * @return {THIS}
902
- */
903
- withPlaceholderTemplate(template, context) {
904
- (/** @type {?} */ (this))._placeholderTemplate = { template, context };
905
- return (/** @type {?} */ (this));
1102
+ array[to] = target;
1103
+ }
1104
+ /**
1105
+ * Moves an item from one array to another.
1106
+ * @template T
1107
+ * @param {?} currentArray Array from which to transfer the item.
1108
+ * @param {?} targetArray Array into which to put the item.
1109
+ * @param {?} currentIndex Index of the item in its current array.
1110
+ * @param {?} targetIndex Index at which to insert the item.
1111
+ * @return {?}
1112
+ */
1113
+ function transferArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
1114
+ /** @type {?} */
1115
+ const from = clamp$1(currentIndex, currentArray.length - 1);
1116
+ /** @type {?} */
1117
+ const to = clamp$1(targetIndex, targetArray.length);
1118
+ if (currentArray.length) {
1119
+ targetArray.splice(to, 0, currentArray.splice(from, 1)[0]);
906
1120
  }
907
- /**
908
- * Sets an alternate drag root element. The root element is the element that will be moved as
909
- * the user is dragging. Passing an alternate root element is useful when trying to enable
910
- * dragging on an element that you might not have access to.
911
- * @template THIS
912
- * @this {THIS}
913
- * @param {?} rootElement
914
- * @return {THIS}
915
- */
916
- withRootElement(rootElement) {
917
- /** @type {?} */
918
- const element = coerceElement(rootElement);
919
- if (element !== (/** @type {?} */ (this))._rootElement) {
920
- if ((/** @type {?} */ (this))._rootElement) {
921
- (/** @type {?} */ (this))._removeRootElementListeners((/** @type {?} */ (this))._rootElement);
922
- }
923
- element.addEventListener('mousedown', (/** @type {?} */ (this))._pointerDown, activeEventListenerOptions);
924
- element.addEventListener('touchstart', (/** @type {?} */ (this))._pointerDown, passiveEventListenerOptions);
925
- (/** @type {?} */ (this))._initialTransform = undefined;
926
- (/** @type {?} */ (this))._rootElement = element;
927
- }
928
- return (/** @type {?} */ (this));
1121
+ }
1122
+ /**
1123
+ * Copies an item from one array to another, leaving it in its
1124
+ * original position in current array.
1125
+ * @template T
1126
+ * @param {?} currentArray Array from which to copy the item.
1127
+ * @param {?} targetArray Array into which is copy the item.
1128
+ * @param {?} currentIndex Index of the item in its current array.
1129
+ * @param {?} targetIndex Index at which to insert the item.
1130
+ *
1131
+ * @return {?}
1132
+ */
1133
+ function copyArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
1134
+ /** @type {?} */
1135
+ const to = clamp$1(targetIndex, targetArray.length);
1136
+ if (currentArray.length) {
1137
+ targetArray.splice(to, 0, currentArray[currentIndex]);
929
1138
  }
930
- /**
931
- * Element to which the draggable's position will be constrained.
932
- * @template THIS
933
- * @this {THIS}
934
- * @param {?} boundaryElement
935
- * @return {THIS}
1139
+ }
1140
+ /**
1141
+ * Clamps a number between zero and a maximum.
1142
+ * @param {?} value
1143
+ * @param {?} max
1144
+ * @return {?}
1145
+ */
1146
+ function clamp$1(value, max) {
1147
+ return Math.max(0, Math.min(max, value));
1148
+ }
1149
+
1150
+ /**
1151
+ * @fileoverview added by tsickle
1152
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
1153
+ */
1154
+ /**
1155
+ * Counter used to generate unique ids for drop refs.
1156
+ * @type {?}
1157
+ */
1158
+ let _uniqueIdCounter = 0;
1159
+ /**
1160
+ * Proximity, as a ratio to width/height, at which a
1161
+ * dragged item will affect the drop container.
1162
+ * @type {?}
1163
+ */
1164
+ const DROP_PROXIMITY_THRESHOLD = 0.05;
1165
+ /**
1166
+ * Reference to a drop list. Used to manipulate or dispose of the container.
1167
+ * \@docs-private
1168
+ * @template T
1169
+ */
1170
+ class DropListRef {
1171
+ /**
1172
+ * @param {?} element
1173
+ * @param {?} _dragDropRegistry
1174
+ * @param {?} _document
936
1175
  */
937
- withBoundaryElement(boundaryElement) {
938
- (/** @type {?} */ (this))._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
939
- return (/** @type {?} */ (this));
1176
+ constructor(element, _dragDropRegistry, _document) {
1177
+ this._dragDropRegistry = _dragDropRegistry;
1178
+ /**
1179
+ * Unique ID for the drop list.
1180
+ * @deprecated No longer being used. To be removed.
1181
+ * \@breaking-change 8.0.0
1182
+ */
1183
+ this.id = `cdk-drop-list-ref-${_uniqueIdCounter++}`;
1184
+ /**
1185
+ * Whether starting a dragging sequence from this container is disabled.
1186
+ */
1187
+ this.disabled = false;
1188
+ /**
1189
+ * Function that is used to determine whether an item
1190
+ * is allowed to be moved into a drop container.
1191
+ */
1192
+ this.enterPredicate = () => true;
1193
+ /**
1194
+ * Emits right before dragging has started.
1195
+ */
1196
+ this.beforeStarted = new Subject();
1197
+ /**
1198
+ * Emits when the user has moved a new drag item into this container.
1199
+ */
1200
+ this.entered = new Subject();
1201
+ /**
1202
+ * Emits when the user removes an item from the container
1203
+ * by dragging it into another container.
1204
+ */
1205
+ this.exited = new Subject();
1206
+ /**
1207
+ * Emits when the user drops an item inside the container.
1208
+ */
1209
+ this.dropped = new Subject();
1210
+ /**
1211
+ * Emits as the user is swapping items while actively dragging.
1212
+ */
1213
+ this.sorted = new Subject();
1214
+ /**
1215
+ * Whether an item in the list is being dragged.
1216
+ */
1217
+ this._isDragging = false;
1218
+ /**
1219
+ * Cache of the dimensions of all the items inside the container.
1220
+ */
1221
+ this._itemPositions = [];
1222
+ /**
1223
+ * Keeps track of the item that was last swapped with the dragged item, as
1224
+ * well as what direction the pointer was moving in when the swap occured.
1225
+ */
1226
+ this._previousSwap = { drag: (/** @type {?} */ (null)), delta: 0 };
1227
+ /**
1228
+ * Drop lists that are connected to the current one.
1229
+ */
1230
+ this._siblings = [];
1231
+ /**
1232
+ * Direction in which the list is oriented.
1233
+ */
1234
+ this._orientation = 'vertical';
1235
+ /**
1236
+ * Connected siblings that currently have a dragged item.
1237
+ */
1238
+ this._activeSiblings = new Set();
1239
+ /**
1240
+ * Layout direction of the drop list.
1241
+ */
1242
+ this._direction = 'ltr';
1243
+ _dragDropRegistry.registerDropContainer(this);
1244
+ this._document = _document;
1245
+ this.element = element instanceof ElementRef ? element.nativeElement : element;
940
1246
  }
941
1247
  /**
942
- * Removes the dragging functionality from the DOM element.
1248
+ * Removes the drop list functionality from the DOM element.
943
1249
  * @return {?}
944
1250
  */
945
1251
  dispose() {
946
- this._removeRootElementListeners(this._rootElement);
947
- // Do this check before removing from the registry since it'll
948
- // stop being considered as dragged once it is removed.
949
- if (this.isDragging()) {
950
- // Since we move out the element to the end of the body while it's being
951
- // dragged, we have to make sure that it's removed if it gets destroyed.
952
- removeElement(this._rootElement);
953
- }
954
- this._destroyPreview();
955
- this._destroyPlaceholder();
956
- this._dragDropRegistry.removeDragItem(this);
957
- this._removeSubscriptions();
958
1252
  this.beforeStarted.complete();
959
- this.started.complete();
960
- this.released.complete();
961
- this.ended.complete();
962
1253
  this.entered.complete();
963
1254
  this.exited.complete();
964
1255
  this.dropped.complete();
965
- this._moveEvents.complete();
966
- this._handles = [];
967
- this._disabledHandles.clear();
968
- this._boundaryElement = this._rootElement = this._placeholderTemplate =
969
- this._previewTemplate = this._nextSibling = (/** @type {?} */ (null));
1256
+ this.sorted.complete();
1257
+ this._activeSiblings.clear();
1258
+ this._dragDropRegistry.removeDropContainer(this);
970
1259
  }
971
1260
  /**
972
- * Checks whether the element is currently being dragged.
1261
+ * Whether an item from this list is currently being dragged.
973
1262
  * @return {?}
974
1263
  */
975
1264
  isDragging() {
976
- return this._hasStartedDragging && this._dragDropRegistry.isDragging(this);
1265
+ return this._isDragging;
977
1266
  }
978
1267
  /**
979
- * Resets a standalone drag item to its initial position.
1268
+ * Starts dragging an item.
980
1269
  * @return {?}
981
1270
  */
982
- reset() {
983
- this._rootElement.style.transform = this._initialTransform || '';
984
- this._activeTransform = { x: 0, y: 0 };
985
- this._passiveTransform = { x: 0, y: 0 };
1271
+ start() {
1272
+ this.beforeStarted.next();
1273
+ this._isDragging = true;
1274
+ this._activeDraggables = this._draggables.slice();
1275
+ this._cacheOwnPosition();
1276
+ this._cacheItemPositions();
1277
+ this._siblings.forEach(sibling => sibling._startReceiving(this));
986
1278
  }
987
1279
  /**
988
- * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.
989
- * @param {?} handle Handle element that should be disabled.
1280
+ * Emits an event to indicate that the user moved an item into the container.
1281
+ * @param {?} item Item that was moved into the container.
1282
+ * @param {?} pointerX Position of the item along the X axis.
1283
+ * @param {?} pointerY Position of the item along the Y axis.
990
1284
  * @return {?}
991
1285
  */
992
- disableHandle(handle) {
993
- if (this._handles.indexOf(handle) > -1) {
994
- this._disabledHandles.add(handle);
1286
+ enter(item, pointerX, pointerY) {
1287
+ this.entered.next({ item, container: this });
1288
+ this.start();
1289
+ // We use the coordinates of where the item entered the drop
1290
+ // zone to figure out at which index it should be inserted.
1291
+ /** @type {?} */
1292
+ const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
1293
+ /** @type {?} */
1294
+ const currentIndex = this._activeDraggables.indexOf(item);
1295
+ /** @type {?} */
1296
+ const newPositionReference = this._activeDraggables[newIndex];
1297
+ /** @type {?} */
1298
+ const placeholder = item.getPlaceholderElement();
1299
+ // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
1300
+ // into another container and back again), we have to ensure that it isn't duplicated.
1301
+ if (currentIndex > -1) {
1302
+ this._activeDraggables.splice(currentIndex, 1);
1303
+ }
1304
+ // Don't use items that are being dragged as a reference, because
1305
+ // their element has been moved down to the bottom of the body.
1306
+ if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
1307
+ /** @type {?} */
1308
+ const element = newPositionReference.getRootElement();
1309
+ (/** @type {?} */ (element.parentElement)).insertBefore(placeholder, element);
1310
+ this._activeDraggables.splice(newIndex, 0, item);
1311
+ }
1312
+ else {
1313
+ this.element.appendChild(placeholder);
1314
+ this._activeDraggables.push(item);
995
1315
  }
1316
+ // The transform needs to be cleared so it doesn't throw off the measurements.
1317
+ placeholder.style.transform = '';
1318
+ // Note that the positions were already cached when we called `start` above,
1319
+ // but we need to refresh them since the amount of items has changed.
1320
+ this._cacheItemPositions();
996
1321
  }
997
1322
  /**
998
- * Enables a handle, if it has been disabled.
999
- * @param {?} handle Handle element to be enabled.
1323
+ * Removes an item from the container after it was dragged into another container by the user.
1324
+ * @param {?} item Item that was dragged out.
1000
1325
  * @return {?}
1001
1326
  */
1002
- enableHandle(handle) {
1003
- this._disabledHandles.delete(handle);
1327
+ exit(item) {
1328
+ this._reset();
1329
+ this.exited.next({ item, container: this });
1004
1330
  }
1005
1331
  /**
1006
- * Unsubscribes from the global subscriptions.
1007
- * @private
1332
+ * Drops an item into this container.
1333
+ * @param {?} item Item being dropped into the container.
1334
+ * @param {?} currentIndex Index at which the item should be inserted.
1335
+ * @param {?} previousContainer Container from which the item got dragged in.
1336
+ * @param {?} isPointerOverContainer Whether the user's pointer was over the
1337
+ * container when the item was dropped.
1008
1338
  * @return {?}
1009
1339
  */
1010
- _removeSubscriptions() {
1011
- this._pointerMoveSubscription.unsubscribe();
1012
- this._pointerUpSubscription.unsubscribe();
1340
+ drop(item, currentIndex, previousContainer, isPointerOverContainer) {
1341
+ this._reset();
1342
+ this.dropped.next({
1343
+ item,
1344
+ currentIndex,
1345
+ previousIndex: previousContainer.getItemIndex(item),
1346
+ container: this,
1347
+ previousContainer,
1348
+ isPointerOverContainer
1349
+ });
1013
1350
  }
1014
1351
  /**
1015
- * Destroys the preview element and its ViewRef.
1016
- * @private
1017
- * @return {?}
1352
+ * Sets the draggable items that are a part of this list.
1353
+ * @template THIS
1354
+ * @this {THIS}
1355
+ * @param {?} items Items that are a part of this list.
1356
+ * @return {THIS}
1018
1357
  */
1019
- _destroyPreview() {
1020
- if (this._preview) {
1021
- removeElement(this._preview);
1022
- }
1023
- if (this._previewRef) {
1024
- this._previewRef.destroy();
1025
- }
1026
- this._preview = this._previewRef = (/** @type {?} */ (null));
1358
+ withItems(items) {
1359
+ (/** @type {?} */ (this))._draggables = items.slice();
1360
+ items.forEach(item => item._withDropContainer((/** @type {?} */ (this))));
1361
+ return (/** @type {?} */ (this));
1027
1362
  }
1028
1363
  /**
1029
- * Destroys the placeholder element and its ViewRef.
1030
- * @private
1031
- * @return {?}
1364
+ * Sets the layout direction of the drop list.
1365
+ * @template THIS
1366
+ * @this {THIS}
1367
+ * @param {?} direction
1368
+ * @return {THIS}
1032
1369
  */
1033
- _destroyPlaceholder() {
1034
- if (this._placeholder) {
1035
- removeElement(this._placeholder);
1036
- }
1037
- if (this._placeholderRef) {
1038
- this._placeholderRef.destroy();
1039
- }
1040
- this._placeholder = this._placeholderRef = (/** @type {?} */ (null));
1370
+ withDirection(direction) {
1371
+ (/** @type {?} */ (this))._direction = direction;
1372
+ return (/** @type {?} */ (this));
1041
1373
  }
1042
1374
  /**
1043
- * Starts the dragging sequence.
1044
- * @private
1045
- * @param {?} event
1046
- * @return {?}
1047
- */
1048
- _startDragSequence(event) {
1049
- // Emit the event on the item before the one on the container.
1050
- this.started.next({ source: this });
1051
- if (isTouchEvent(event)) {
1052
- this._lastTouchEventTime = Date.now();
1053
- }
1054
- if (this.dropContainer) {
1055
- /** @type {?} */
1056
- const element = this._rootElement;
1057
- // Grab the `nextSibling` before the preview and placeholder
1058
- // have been created so we don't get the preview by accident.
1059
- this._nextSibling = element.nextSibling;
1060
- /** @type {?} */
1061
- const preview = this._preview = this._createPreviewElement();
1062
- /** @type {?} */
1063
- const placeholder = this._placeholder = this._createPlaceholderElement();
1064
- // We move the element out at the end of the body and we make it hidden, because keeping it in
1065
- // place will throw off the consumer's `:last-child` selectors. We can't remove the element
1066
- // from the DOM completely, because iOS will stop firing all subsequent events in the chain.
1067
- element.style.display = 'none';
1068
- this._document.body.appendChild((/** @type {?} */ (element.parentNode)).replaceChild(placeholder, element));
1069
- this._document.body.appendChild(preview);
1070
- this.dropContainer.start();
1071
- }
1375
+ * Sets the containers that are connected to this one. When two or more containers are
1376
+ * connected, the user will be allowed to transfer items between them.
1377
+ * @template THIS
1378
+ * @this {THIS}
1379
+ * @param {?} connectedTo Other containers that the current containers should be connected to.
1380
+ * @return {THIS}
1381
+ */
1382
+ connectedTo(connectedTo) {
1383
+ (/** @type {?} */ (this))._siblings = connectedTo.slice();
1384
+ return (/** @type {?} */ (this));
1072
1385
  }
1073
1386
  /**
1074
- * Sets up the different variables and subscriptions
1075
- * that will be necessary for the dragging sequence.
1076
- * @private
1077
- * @param {?} referenceElement Element that started the drag sequence.
1078
- * @param {?} event Browser event object that started the sequence.
1079
- * @return {?}
1387
+ * Sets the orientation of the container.
1388
+ * @template THIS
1389
+ * @this {THIS}
1390
+ * @param {?} orientation New orientation for the container.
1391
+ * @return {THIS}
1080
1392
  */
1081
- _initializeDragSequence(referenceElement, event) {
1082
- // Always stop propagation for the event that initializes
1083
- // the dragging sequence, in order to prevent it from potentially
1084
- // starting another sequence for a draggable parent somewhere up the DOM tree.
1085
- event.stopPropagation();
1086
- /** @type {?} */
1087
- const isDragging = this.isDragging();
1088
- /** @type {?} */
1089
- const isTouchSequence = isTouchEvent(event);
1090
- /** @type {?} */
1091
- const isAuxiliaryMouseButton = !isTouchSequence && ((/** @type {?} */ (event))).button !== 0;
1092
- /** @type {?} */
1093
- const rootElement = this._rootElement;
1094
- /** @type {?} */
1095
- const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime &&
1096
- this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
1097
- // If the event started from an element with the native HTML drag&drop, it'll interfere
1098
- // with our own dragging (e.g. `img` tags do it by default). Prevent the default action
1099
- // to stop it from happening. Note that preventing on `dragstart` also seems to work, but
1100
- // it's flaky and it fails if the user drags it away quickly. Also note that we only want
1101
- // to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
1102
- // events from firing on touch devices.
1103
- if (event.target && ((/** @type {?} */ (event.target))).draggable && event.type === 'mousedown') {
1104
- event.preventDefault();
1105
- }
1106
- // Abort if the user is already dragging or is using a mouse button other than the primary one.
1107
- if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) {
1108
- return;
1109
- }
1110
- // Cache the previous transform amount only after the first drag sequence, because
1111
- // we don't want our own transforms to stack on top of each other.
1112
- if (this._initialTransform == null) {
1113
- this._initialTransform = this._rootElement.style.transform || '';
1114
- }
1115
- // If we've got handles, we need to disable the tap highlight on the entire root element,
1116
- // otherwise iOS will still add it, even though all the drag interactions on the handle
1117
- // are disabled.
1118
- if (this._handles.length) {
1119
- this._rootElementTapHighlight = rootElement.style.webkitTapHighlightColor;
1120
- rootElement.style.webkitTapHighlightColor = 'transparent';
1121
- }
1122
- this._toggleNativeDragInteractions();
1123
- this._hasStartedDragging = this._hasMoved = false;
1124
- this._initialContainer = (/** @type {?} */ (this.dropContainer));
1125
- this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
1126
- this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
1127
- this._scrollPosition = this._viewportRuler.getViewportScrollPosition();
1128
- if (this._boundaryElement) {
1129
- this._boundaryRect = this._boundaryElement.getBoundingClientRect();
1130
- }
1131
- // If we have a custom preview template, the element won't be visible anyway so we avoid the
1132
- // extra `getBoundingClientRect` calls and just move the preview next to the cursor.
1133
- this._pickupPositionInElement = this._previewTemplate && this._previewTemplate.template ?
1134
- { x: 0, y: 0 } :
1135
- this._getPointerPositionInElement(referenceElement, event);
1136
- /** @type {?} */
1137
- const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
1138
- this._pointerDirectionDelta = { x: 0, y: 0 };
1139
- this._pointerPositionAtLastDirectionChange = { x: pointerPosition.x, y: pointerPosition.y };
1140
- this._dragDropRegistry.startDragging(this, event);
1393
+ withOrientation(orientation) {
1394
+ (/** @type {?} */ (this))._orientation = orientation;
1395
+ return (/** @type {?} */ (this));
1141
1396
  }
1142
1397
  /**
1143
- * Cleans up the DOM artifacts that were added to facilitate the element being dragged.
1144
- * @private
1145
- * @param {?} event
1398
+ * Figures out the index of an item in the container.
1399
+ * @param {?} item Item whose index should be determined.
1146
1400
  * @return {?}
1147
1401
  */
1148
- _cleanupDragArtifacts(event) {
1149
- // Restore the element's visibility and insert it at its old position in the DOM.
1150
- // It's important that we maintain the position, because moving the element around in the DOM
1151
- // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
1152
- // while moving the existing elements in all other cases.
1153
- this._rootElement.style.display = '';
1154
- if (this._nextSibling) {
1155
- (/** @type {?} */ (this._nextSibling.parentNode)).insertBefore(this._rootElement, this._nextSibling);
1156
- }
1157
- else {
1158
- this._initialContainer.element.nativeElement.appendChild(this._rootElement);
1402
+ getItemIndex(item) {
1403
+ if (!this._isDragging) {
1404
+ return this._draggables.indexOf(item);
1159
1405
  }
1160
- this._destroyPreview();
1161
- this._destroyPlaceholder();
1162
- this._boundaryRect = this._previewRect = undefined;
1163
- // Re-enter the NgZone since we bound `document` events on the outside.
1164
- this._ngZone.run(() => {
1165
- /** @type {?} */
1166
- const container = (/** @type {?} */ (this.dropContainer));
1167
- /** @type {?} */
1168
- const currentIndex = container.getItemIndex(this);
1169
- const { x, y } = this._getPointerPositionOnPage(event);
1170
- /** @type {?} */
1171
- const isPointerOverContainer = container._isOverContainer(x, y);
1172
- this.ended.next({ source: this });
1173
- this.dropped.next({
1174
- item: this,
1175
- currentIndex,
1176
- previousIndex: this._initialContainer.getItemIndex(this),
1177
- container: container,
1178
- previousContainer: this._initialContainer,
1179
- isPointerOverContainer
1180
- });
1181
- container.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
1182
- this.dropContainer = this._initialContainer;
1183
- });
1406
+ // Items are sorted always by top/left in the cache, however they flow differently in RTL.
1407
+ // The rest of the logic still stands no matter what orientation we're in, however
1408
+ // we need to invert the array when determining the index.
1409
+ /** @type {?} */
1410
+ const items = this._orientation === 'horizontal' && this._direction === 'rtl' ?
1411
+ this._itemPositions.slice().reverse() : this._itemPositions;
1412
+ return findIndex(items, currentItem => currentItem.drag === item);
1184
1413
  }
1185
1414
  /**
1186
- * Updates the item's position in its drop container, or moves it
1187
- * into a new one, depending on its current drag position.
1188
- * @private
1189
- * @param {?} __0
1415
+ * Whether the list is able to receive the item that
1416
+ * is currently being dragged inside a connected drop list.
1190
1417
  * @return {?}
1191
1418
  */
1192
- _updateActiveDropContainer({ x, y }) {
1193
- // Drop container that draggable has been moved into.
1194
- /** @type {?} */
1195
- let newContainer = (/** @type {?} */ (this.dropContainer))._getSiblingContainerFromPosition(this, x, y) ||
1196
- this._initialContainer._getSiblingContainerFromPosition(this, x, y);
1197
- // If we couldn't find a new container to move the item into, and the item has left it's
1198
- // initial container, check whether the it's over the initial container. This handles the
1199
- // case where two containers are connected one way and the user tries to undo dragging an
1200
- // item into a new container.
1201
- if (!newContainer && this.dropContainer !== this._initialContainer &&
1202
- this._initialContainer._isOverContainer(x, y)) {
1203
- newContainer = this._initialContainer;
1204
- }
1205
- if (newContainer && newContainer !== this.dropContainer) {
1206
- this._ngZone.run(() => {
1207
- // Notify the old container that the item has left.
1208
- this.exited.next({ item: this, container: (/** @type {?} */ (this.dropContainer)) });
1209
- (/** @type {?} */ (this.dropContainer)).exit(this);
1210
- // Notify the new container that the item has entered.
1211
- this.entered.next({ item: this, container: (/** @type {?} */ (newContainer)) });
1212
- this.dropContainer = (/** @type {?} */ (newContainer));
1213
- this.dropContainer.enter(this, x, y);
1214
- });
1215
- }
1216
- (/** @type {?} */ (this.dropContainer))._sortItem(this, x, y, this._pointerDirectionDelta);
1217
- this._preview.style.transform =
1218
- getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
1419
+ isReceiving() {
1420
+ return this._activeSiblings.size > 0;
1219
1421
  }
1220
1422
  /**
1221
- * Creates the element that will be rendered next to the user's pointer
1222
- * and will be used as a preview of the element that is being dragged.
1223
- * @private
1423
+ * Sorts an item inside the container based on its position.
1424
+ * @param {?} item Item to be sorted.
1425
+ * @param {?} pointerX Position of the item along the X axis.
1426
+ * @param {?} pointerY Position of the item along the Y axis.
1427
+ * @param {?} pointerDelta Direction in which the pointer is moving along each axis.
1224
1428
  * @return {?}
1225
1429
  */
1226
- _createPreviewElement() {
1430
+ _sortItem(item, pointerX, pointerY, pointerDelta) {
1431
+ // Don't sort the item if it's out of range.
1432
+ if (!this._isPointerNearDropContainer(pointerX, pointerY)) {
1433
+ return;
1434
+ }
1227
1435
  /** @type {?} */
1228
- const previewTemplate = this._previewTemplate;
1436
+ const siblings = this._itemPositions;
1229
1437
  /** @type {?} */
1230
- let preview;
1231
- if (previewTemplate && previewTemplate.template) {
1232
- /** @type {?} */
1233
- const viewRef = this._viewContainerRef.createEmbeddedView(previewTemplate.template, previewTemplate.context);
1234
- preview = viewRef.rootNodes[0];
1235
- this._previewRef = viewRef;
1236
- preview.style.transform =
1237
- getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
1438
+ const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
1439
+ if (newIndex === -1 && siblings.length > 0) {
1440
+ return;
1238
1441
  }
1239
- else {
1442
+ /** @type {?} */
1443
+ const isHorizontal = this._orientation === 'horizontal';
1444
+ /** @type {?} */
1445
+ const currentIndex = findIndex(siblings, currentItem => currentItem.drag === item);
1446
+ /** @type {?} */
1447
+ const siblingAtNewPosition = siblings[newIndex];
1448
+ /** @type {?} */
1449
+ const currentPosition = siblings[currentIndex].clientRect;
1450
+ /** @type {?} */
1451
+ const newPosition = siblingAtNewPosition.clientRect;
1452
+ /** @type {?} */
1453
+ const delta = currentIndex > newIndex ? 1 : -1;
1454
+ this._previousSwap.drag = siblingAtNewPosition.drag;
1455
+ this._previousSwap.delta = isHorizontal ? pointerDelta.x : pointerDelta.y;
1456
+ // How many pixels the item's placeholder should be offset.
1457
+ /** @type {?} */
1458
+ const itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta);
1459
+ // How many pixels all the other items should be offset.
1460
+ /** @type {?} */
1461
+ const siblingOffset = this._getSiblingOffsetPx(currentIndex, siblings, delta);
1462
+ // Save the previous order of the items before moving the item to its new index.
1463
+ // We use this to check whether an item has been moved as a result of the sorting.
1464
+ /** @type {?} */
1465
+ const oldOrder = siblings.slice();
1466
+ // Shuffle the array in place.
1467
+ moveItemInArray(siblings, currentIndex, newIndex);
1468
+ this.sorted.next({
1469
+ previousIndex: currentIndex,
1470
+ currentIndex: newIndex,
1471
+ container: this,
1472
+ item
1473
+ });
1474
+ siblings.forEach((sibling, index) => {
1475
+ // Don't do anything if the position hasn't changed.
1476
+ if (oldOrder[index] === sibling) {
1477
+ return;
1478
+ }
1240
1479
  /** @type {?} */
1241
- const element = this._rootElement;
1480
+ const isDraggedItem = sibling.drag === item;
1242
1481
  /** @type {?} */
1243
- const elementRect = element.getBoundingClientRect();
1244
- preview = deepCloneNode(element);
1245
- preview.style.width = `${elementRect.width}px`;
1246
- preview.style.height = `${elementRect.height}px`;
1247
- preview.style.transform = getTransform(elementRect.left, elementRect.top);
1248
- }
1249
- extendStyles(preview.style, {
1250
- // It's important that we disable the pointer events on the preview, because
1251
- // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
1252
- pointerEvents: 'none',
1253
- position: 'fixed',
1254
- top: '0',
1255
- left: '0',
1256
- zIndex: '1000'
1482
+ const offset = isDraggedItem ? itemOffset : siblingOffset;
1483
+ /** @type {?} */
1484
+ const elementToOffset = isDraggedItem ? item.getPlaceholderElement() :
1485
+ sibling.drag.getRootElement();
1486
+ // Update the offset to reflect the new position.
1487
+ sibling.offset += offset;
1488
+ // Since we're moving the items with a `transform`, we need to adjust their cached
1489
+ // client rects to reflect their new position, as well as swap their positions in the cache.
1490
+ // Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the
1491
+ // elements may be mid-animation which will give us a wrong result.
1492
+ if (isHorizontal) {
1493
+ // Round the transforms since some browsers will
1494
+ // blur the elements, for sub-pixel transforms.
1495
+ elementToOffset.style.transform = `translate3d(${Math.round(sibling.offset)}px, 0, 0)`;
1496
+ adjustClientRect(sibling.clientRect, 0, offset);
1497
+ }
1498
+ else {
1499
+ elementToOffset.style.transform = `translate3d(0, ${Math.round(sibling.offset)}px, 0)`;
1500
+ adjustClientRect(sibling.clientRect, offset, 0);
1501
+ }
1257
1502
  });
1258
- toggleNativeDragInteractions(preview, false);
1259
- preview.classList.add('cdk-drag-preview');
1260
- preview.setAttribute('dir', this._dir ? this._dir.value : 'ltr');
1261
- return preview;
1262
1503
  }
1263
1504
  /**
1264
- * Animates the preview element from its current position to the location of the drop placeholder.
1505
+ * Caches the position of the drop list.
1506
+ * @private
1507
+ * @return {?}
1508
+ */
1509
+ _cacheOwnPosition() {
1510
+ this._clientRect = this.element.getBoundingClientRect();
1511
+ }
1512
+ /**
1513
+ * Refreshes the position cache of the items and sibling containers.
1265
1514
  * @private
1266
- * @return {?} Promise that resolves when the animation completes.
1515
+ * @return {?}
1267
1516
  */
1268
- _animatePreviewToPlaceholder() {
1269
- // If the user hasn't moved yet, the transitionend event won't fire.
1270
- if (!this._hasMoved) {
1271
- return Promise.resolve();
1272
- }
1273
- /** @type {?} */
1274
- const placeholderRect = this._placeholder.getBoundingClientRect();
1275
- // Apply the class that adds a transition to the preview.
1276
- this._preview.classList.add('cdk-drag-animating');
1277
- // Move the preview to the placeholder position.
1278
- this._preview.style.transform = getTransform(placeholderRect.left, placeholderRect.top);
1279
- // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since
1280
- // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
1281
- // apply its style, we take advantage of the available info to figure out whether we need to
1282
- // bind the event in the first place.
1517
+ _cacheItemPositions() {
1283
1518
  /** @type {?} */
1284
- const duration = getTransformTransitionDurationInMs(this._preview);
1285
- if (duration === 0) {
1286
- return Promise.resolve();
1287
- }
1288
- return this._ngZone.runOutsideAngular(() => {
1289
- return new Promise(resolve => {
1290
- /** @type {?} */
1291
- const handler = (/** @type {?} */ (((event) => {
1292
- if (!event || (event.target === this._preview && event.propertyName === 'transform')) {
1293
- this._preview.removeEventListener('transitionend', handler);
1294
- resolve();
1295
- clearTimeout(timeout);
1296
- }
1297
- })));
1298
- // If a transition is short enough, the browser might not fire the `transitionend` event.
1299
- // Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
1300
- // fire if the transition hasn't completed when it was supposed to.
1301
- /** @type {?} */
1302
- const timeout = setTimeout((/** @type {?} */ (handler)), duration * 1.5);
1303
- this._preview.addEventListener('transitionend', handler);
1304
- });
1519
+ const isHorizontal = this._orientation === 'horizontal';
1520
+ this._itemPositions = this._activeDraggables.map(drag => {
1521
+ /** @type {?} */
1522
+ const elementToMeasure = this._dragDropRegistry.isDragging(drag) ?
1523
+ // If the element is being dragged, we have to measure the
1524
+ // placeholder, because the element is hidden.
1525
+ drag.getPlaceholderElement() :
1526
+ drag.getRootElement();
1527
+ /** @type {?} */
1528
+ const clientRect = elementToMeasure.getBoundingClientRect();
1529
+ return {
1530
+ drag,
1531
+ offset: 0,
1532
+ // We need to clone the `clientRect` here, because all the values on it are readonly
1533
+ // and we need to be able to update them. Also we can't use a spread here, because
1534
+ // the values on a `ClientRect` aren't own properties. See:
1535
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
1536
+ clientRect: {
1537
+ top: clientRect.top,
1538
+ right: clientRect.right,
1539
+ bottom: clientRect.bottom,
1540
+ left: clientRect.left,
1541
+ width: clientRect.width,
1542
+ height: clientRect.height
1543
+ }
1544
+ };
1545
+ }).sort((a, b) => {
1546
+ return isHorizontal ? a.clientRect.left - b.clientRect.left :
1547
+ a.clientRect.top - b.clientRect.top;
1305
1548
  });
1306
1549
  }
1307
1550
  /**
1308
- * Creates an element that will be shown instead of the current element while dragging.
1551
+ * Resets the container to its initial state.
1309
1552
  * @private
1310
1553
  * @return {?}
1311
1554
  */
1312
- _createPlaceholderElement() {
1313
- /** @type {?} */
1314
- const placeholderTemplate = this._placeholderTemplate;
1315
- /** @type {?} */
1316
- let placeholder;
1317
- if (placeholderTemplate && placeholderTemplate.template) {
1318
- this._placeholderRef = this._viewContainerRef.createEmbeddedView(placeholderTemplate.template, placeholderTemplate.context);
1319
- placeholder = this._placeholderRef.rootNodes[0];
1320
- }
1321
- else {
1322
- placeholder = deepCloneNode(this._rootElement);
1323
- }
1324
- placeholder.classList.add('cdk-drag-placeholder');
1325
- return placeholder;
1555
+ _reset() {
1556
+ this._isDragging = false;
1557
+ // TODO(crisbeto): may have to wait for the animations to finish.
1558
+ this._activeDraggables.forEach(item => item.getRootElement().style.transform = '');
1559
+ this._siblings.forEach(sibling => sibling._stopReceiving(this));
1560
+ this._activeDraggables = [];
1561
+ this._itemPositions = [];
1562
+ this._previousSwap.drag = null;
1563
+ this._previousSwap.delta = 0;
1326
1564
  }
1327
1565
  /**
1328
- * Figures out the coordinates at which an element was picked up.
1566
+ * Gets the offset in pixels by which the items that aren't being dragged should be moved.
1329
1567
  * @private
1330
- * @param {?} referenceElement Element that initiated the dragging.
1331
- * @param {?} event Event that initiated the dragging.
1568
+ * @param {?} currentIndex Index of the item currently being dragged.
1569
+ * @param {?} siblings All of the items in the list.
1570
+ * @param {?} delta Direction in which the user is moving.
1332
1571
  * @return {?}
1333
1572
  */
1334
- _getPointerPositionInElement(referenceElement, event) {
1335
- /** @type {?} */
1336
- const elementRect = this._rootElement.getBoundingClientRect();
1337
- /** @type {?} */
1338
- const handleElement = referenceElement === this._rootElement ? null : referenceElement;
1573
+ _getSiblingOffsetPx(currentIndex, siblings, delta) {
1339
1574
  /** @type {?} */
1340
- const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect;
1575
+ const isHorizontal = this._orientation === 'horizontal';
1341
1576
  /** @type {?} */
1342
- const point = isTouchEvent(event) ? event.targetTouches[0] : event;
1577
+ const currentPosition = siblings[currentIndex].clientRect;
1343
1578
  /** @type {?} */
1344
- const x = point.pageX - referenceRect.left - this._scrollPosition.left;
1579
+ const immediateSibling = siblings[currentIndex + delta * -1];
1345
1580
  /** @type {?} */
1346
- const y = point.pageY - referenceRect.top - this._scrollPosition.top;
1347
- return {
1348
- x: referenceRect.left - elementRect.left + x,
1349
- y: referenceRect.top - elementRect.top + y
1350
- };
1581
+ let siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta;
1582
+ if (immediateSibling) {
1583
+ /** @type {?} */
1584
+ const start = isHorizontal ? 'left' : 'top';
1585
+ /** @type {?} */
1586
+ const end = isHorizontal ? 'right' : 'bottom';
1587
+ // Get the spacing between the start of the current item and the end of the one immediately
1588
+ // after it in the direction in which the user is dragging, or vice versa. We add it to the
1589
+ // offset in order to push the element to where it will be when it's inline and is influenced
1590
+ // by the `margin` of its siblings.
1591
+ if (delta === -1) {
1592
+ siblingOffset -= immediateSibling.clientRect[start] - currentPosition[end];
1593
+ }
1594
+ else {
1595
+ siblingOffset += currentPosition[start] - immediateSibling.clientRect[end];
1596
+ }
1597
+ }
1598
+ return siblingOffset;
1351
1599
  }
1352
1600
  /**
1353
- * Determines the point of the page that was touched by the user.
1601
+ * Checks whether the pointer coordinates are close to the drop container.
1354
1602
  * @private
1355
- * @param {?} event
1603
+ * @param {?} pointerX Coordinates along the X axis.
1604
+ * @param {?} pointerY Coordinates along the Y axis.
1356
1605
  * @return {?}
1357
1606
  */
1358
- _getPointerPositionOnPage(event) {
1359
- // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
1607
+ _isPointerNearDropContainer(pointerX, pointerY) {
1608
+ const { top, right, bottom, left, width, height } = this._clientRect;
1360
1609
  /** @type {?} */
1361
- const point = isTouchEvent(event) ? (event.touches[0] || event.changedTouches[0]) : event;
1362
- return {
1363
- x: point.pageX - this._scrollPosition.left,
1364
- y: point.pageY - this._scrollPosition.top
1365
- };
1610
+ const xThreshold = width * DROP_PROXIMITY_THRESHOLD;
1611
+ /** @type {?} */
1612
+ const yThreshold = height * DROP_PROXIMITY_THRESHOLD;
1613
+ return pointerY > top - yThreshold && pointerY < bottom + yThreshold &&
1614
+ pointerX > left - xThreshold && pointerX < right + xThreshold;
1366
1615
  }
1367
1616
  /**
1368
- * Gets the pointer position on the page, accounting for any position constraints.
1617
+ * Gets the offset in pixels by which the item that is being dragged should be moved.
1369
1618
  * @private
1370
- * @param {?} event
1619
+ * @param {?} currentPosition Current position of the item.
1620
+ * @param {?} newPosition Position of the item where the current item should be moved.
1621
+ * @param {?} delta Direction in which the user is moving.
1371
1622
  * @return {?}
1372
1623
  */
1373
- _getConstrainedPointerPosition(event) {
1624
+ _getItemOffsetPx(currentPosition, newPosition, delta) {
1374
1625
  /** @type {?} */
1375
- const point = this._getPointerPositionOnPage(event);
1626
+ const isHorizontal = this._orientation === 'horizontal';
1376
1627
  /** @type {?} */
1377
- const dropContainerLock = this.dropContainer ? this.dropContainer.lockAxis : null;
1378
- if (this.lockAxis === 'x' || dropContainerLock === 'x') {
1379
- point.y = this._pickupPositionOnPage.y;
1380
- }
1381
- else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
1382
- point.x = this._pickupPositionOnPage.x;
1383
- }
1384
- if (this._boundaryRect) {
1385
- const { x: pickupX, y: pickupY } = this._pickupPositionInElement;
1386
- /** @type {?} */
1387
- const boundaryRect = this._boundaryRect;
1388
- /** @type {?} */
1389
- const previewRect = (/** @type {?} */ (this._previewRect));
1390
- /** @type {?} */
1391
- const minY = boundaryRect.top + pickupY;
1392
- /** @type {?} */
1393
- const maxY = boundaryRect.bottom - (previewRect.height - pickupY);
1394
- /** @type {?} */
1395
- const minX = boundaryRect.left + pickupX;
1396
- /** @type {?} */
1397
- const maxX = boundaryRect.right - (previewRect.width - pickupX);
1398
- point.x = clamp$1(point.x, minX, maxX);
1399
- point.y = clamp$1(point.y, minY, maxY);
1628
+ let itemOffset = isHorizontal ? newPosition.left - currentPosition.left :
1629
+ newPosition.top - currentPosition.top;
1630
+ // Account for differences in the item width/height.
1631
+ if (delta === -1) {
1632
+ itemOffset += isHorizontal ? newPosition.width - currentPosition.width :
1633
+ newPosition.height - currentPosition.height;
1400
1634
  }
1401
- return point;
1635
+ return itemOffset;
1402
1636
  }
1403
1637
  /**
1404
- * Updates the current drag delta, based on the user's current pointer position on the page.
1638
+ * Gets the index of an item in the drop container, based on the position of the user's pointer.
1405
1639
  * @private
1406
- * @param {?} pointerPositionOnPage
1640
+ * @param {?} item Item that is being sorted.
1641
+ * @param {?} pointerX Position of the user's pointer along the X axis.
1642
+ * @param {?} pointerY Position of the user's pointer along the Y axis.
1643
+ * @param {?=} delta Direction in which the user is moving their pointer.
1407
1644
  * @return {?}
1408
1645
  */
1409
- _updatePointerDirectionDelta(pointerPositionOnPage) {
1410
- const { x, y } = pointerPositionOnPage;
1411
- /** @type {?} */
1412
- const delta = this._pointerDirectionDelta;
1413
- /** @type {?} */
1414
- const positionSinceLastChange = this._pointerPositionAtLastDirectionChange;
1415
- // Amount of pixels the user has dragged since the last time the direction changed.
1416
- /** @type {?} */
1417
- const changeX = Math.abs(x - positionSinceLastChange.x);
1646
+ _getItemIndexFromPointerPosition(item, pointerX, pointerY, delta) {
1418
1647
  /** @type {?} */
1419
- const changeY = Math.abs(y - positionSinceLastChange.y);
1420
- // Because we handle pointer events on a per-pixel basis, we don't want the delta
1421
- // to change for every pixel, otherwise anything that depends on it can look erratic.
1422
- // To make the delta more consistent, we track how much the user has moved since the last
1423
- // delta change and we only update it after it has reached a certain threshold.
1424
- if (changeX > this._config.pointerDirectionChangeThreshold) {
1425
- delta.x = x > positionSinceLastChange.x ? 1 : -1;
1426
- positionSinceLastChange.x = x;
1648
+ const isHorizontal = this._orientation === 'horizontal';
1649
+ return findIndex(this._itemPositions, ({ drag, clientRect }, _, array) => {
1650
+ if (drag === item) {
1651
+ // If there's only one item left in the container, it must be
1652
+ // the dragged item itself so we use it as a reference.
1653
+ return array.length < 2;
1654
+ }
1655
+ if (delta) {
1656
+ /** @type {?} */
1657
+ const direction = isHorizontal ? delta.x : delta.y;
1658
+ // If the user is still hovering over the same item as last time, and they didn't change
1659
+ // the direction in which they're dragging, we don't consider it a direction swap.
1660
+ if (drag === this._previousSwap.drag && direction === this._previousSwap.delta) {
1661
+ return false;
1662
+ }
1663
+ }
1664
+ return isHorizontal ?
1665
+ // Round these down since most browsers report client rects with
1666
+ // sub-pixel precision, whereas the pointer coordinates are rounded to pixels.
1667
+ pointerX >= Math.floor(clientRect.left) && pointerX <= Math.floor(clientRect.right) :
1668
+ pointerY >= Math.floor(clientRect.top) && pointerY <= Math.floor(clientRect.bottom);
1669
+ });
1670
+ }
1671
+ /**
1672
+ * Checks whether the user's pointer is positioned over the container.
1673
+ * @param {?} x Pointer position along the X axis.
1674
+ * @param {?} y Pointer position along the Y axis.
1675
+ * @return {?}
1676
+ */
1677
+ _isOverContainer(x, y) {
1678
+ return isInsideClientRect(this._clientRect, x, y);
1679
+ }
1680
+ /**
1681
+ * Figures out whether an item should be moved into a sibling
1682
+ * drop container, based on its current position.
1683
+ * @param {?} item Drag item that is being moved.
1684
+ * @param {?} x Position of the item along the X axis.
1685
+ * @param {?} y Position of the item along the Y axis.
1686
+ * @return {?}
1687
+ */
1688
+ _getSiblingContainerFromPosition(item, x, y) {
1689
+ return this._siblings.find(sibling => sibling._canReceive(item, x, y));
1690
+ }
1691
+ /**
1692
+ * Checks whether the drop list can receive the passed-in item.
1693
+ * @param {?} item Item that is being dragged into the list.
1694
+ * @param {?} x Position of the item along the X axis.
1695
+ * @param {?} y Position of the item along the Y axis.
1696
+ * @return {?}
1697
+ */
1698
+ _canReceive(item, x, y) {
1699
+ if (!this.enterPredicate(item, this) || !isInsideClientRect(this._clientRect, x, y)) {
1700
+ return false;
1427
1701
  }
1428
- if (changeY > this._config.pointerDirectionChangeThreshold) {
1429
- delta.y = y > positionSinceLastChange.y ? 1 : -1;
1430
- positionSinceLastChange.y = y;
1702
+ /** @type {?} */
1703
+ const elementFromPoint = this._document.elementFromPoint(x, y);
1704
+ // If there's no element at the pointer position, then
1705
+ // the client rect is probably scrolled out of the view.
1706
+ if (!elementFromPoint) {
1707
+ return false;
1431
1708
  }
1432
- return delta;
1709
+ // The `ClientRect`, that we're using to find the container over which the user is
1710
+ // hovering, doesn't give us any information on whether the element has been scrolled
1711
+ // out of the view or whether it's overlapping with other containers. This means that
1712
+ // we could end up transferring the item into a container that's invisible or is positioned
1713
+ // below another one. We use the result from `elementFromPoint` to get the top-most element
1714
+ // at the pointer position and to find whether it's one of the intersecting drop containers.
1715
+ return elementFromPoint === this.element || this.element.contains(elementFromPoint);
1433
1716
  }
1434
1717
  /**
1435
- * Toggles the native drag interactions, based on how many handles are registered.
1436
- * @private
1718
+ * Called by one of the connected drop lists when a dragging sequence has started.
1719
+ * @param {?} sibling Sibling in which dragging has started.
1437
1720
  * @return {?}
1438
1721
  */
1439
- _toggleNativeDragInteractions() {
1440
- if (!this._rootElement || !this._handles) {
1441
- return;
1442
- }
1722
+ _startReceiving(sibling) {
1443
1723
  /** @type {?} */
1444
- const shouldEnable = this.disabled || this._handles.length > 0;
1445
- if (shouldEnable !== this._nativeInteractionsEnabled) {
1446
- this._nativeInteractionsEnabled = shouldEnable;
1447
- toggleNativeDragInteractions(this._rootElement, shouldEnable);
1724
+ const activeSiblings = this._activeSiblings;
1725
+ if (!activeSiblings.has(sibling)) {
1726
+ activeSiblings.add(sibling);
1727
+ this._cacheOwnPosition();
1448
1728
  }
1449
1729
  }
1450
1730
  /**
1451
- * Removes the manually-added event listeners from the root element.
1452
- * @private
1453
- * @param {?} element
1731
+ * Called by a connected drop list when dragging has stopped.
1732
+ * @param {?} sibling Sibling whose dragging has stopped.
1454
1733
  * @return {?}
1455
1734
  */
1456
- _removeRootElementListeners(element) {
1457
- element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
1458
- element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
1735
+ _stopReceiving(sibling) {
1736
+ this._activeSiblings.delete(sibling);
1459
1737
  }
1460
1738
  }
1461
1739
  /**
1462
- * Gets a 3d `transform` that can be applied to an element.
1463
- * @param {?} x Desired position of the element along the X axis.
1464
- * @param {?} y Desired position of the element along the Y axis.
1465
- * @return {?}
1466
- */
1467
- function getTransform(x, y) {
1468
- // Round the transforms since some browsers will
1469
- // blur the elements for sub-pixel transforms.
1470
- return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
1471
- }
1472
- /**
1473
- * Creates a deep clone of an element.
1474
- * @param {?} node
1475
- * @return {?}
1476
- */
1477
- function deepCloneNode(node) {
1478
- /** @type {?} */
1479
- const clone = (/** @type {?} */ (node.cloneNode(true)));
1480
- // Remove the `id` to avoid having multiple elements with the same id on the page.
1481
- clone.removeAttribute('id');
1482
- return clone;
1483
- }
1484
- /**
1485
- * Clamps a value between a minimum and a maximum.
1486
- * @param {?} value
1487
- * @param {?} min
1488
- * @param {?} max
1740
+ * Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
1741
+ * @param {?} clientRect `ClientRect` that should be updated.
1742
+ * @param {?} top Amount to add to the `top` position.
1743
+ * @param {?} left Amount to add to the `left` position.
1489
1744
  * @return {?}
1490
1745
  */
1491
- function clamp$1(value, min, max) {
1492
- return Math.max(min, Math.min(max, value));
1746
+ function adjustClientRect(clientRect, top, left) {
1747
+ clientRect.top += top;
1748
+ clientRect.bottom = clientRect.top + clientRect.height;
1749
+ clientRect.left += left;
1750
+ clientRect.right = clientRect.left + clientRect.width;
1493
1751
  }
1494
1752
  /**
1495
- * Helper to remove an element from the DOM and to do all the necessary null checks.
1496
- * @param {?} element Element to be removed.
1753
+ * Finds the index of an item that matches a predicate function. Used as an equivalent
1754
+ * of `Array.prototype.find` which isn't part of the standard Google typings.
1755
+ * @template T
1756
+ * @param {?} array Array in which to look for matches.
1757
+ * @param {?} predicate Function used to determine whether an item is a match.
1497
1758
  * @return {?}
1498
1759
  */
1499
- function removeElement(element) {
1500
- if (element && element.parentNode) {
1501
- element.parentNode.removeChild(element);
1760
+ function findIndex(array, predicate) {
1761
+ for (let i = 0; i < array.length; i++) {
1762
+ if (predicate(array[i], i, array)) {
1763
+ return i;
1764
+ }
1502
1765
  }
1766
+ return -1;
1503
1767
  }
1504
1768
  /**
1505
- * Determines whether an event is a touch event.
1506
- * @param {?} event
1769
+ * Checks whether some coordinates are within a `ClientRect`.
1770
+ * @param {?} clientRect ClientRect that is being checked.
1771
+ * @param {?} x Coordinates along the X axis.
1772
+ * @param {?} y Coordinates along the Y axis.
1507
1773
  * @return {?}
1508
1774
  */
1509
- function isTouchEvent(event) {
1510
- return event.type.startsWith('touch');
1775
+ function isInsideClientRect(clientRect, x, y) {
1776
+ const { top, bottom, left, right } = clientRect;
1777
+ return y >= top && y <= bottom && x >= left && x <= right;
1511
1778
  }
1512
1779
 
1513
1780
  /**
@@ -1515,1000 +1782,850 @@ function isTouchEvent(event) {
1515
1782
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
1516
1783
  */
1517
1784
  /**
1518
- * Injection token that can be used to configure the behavior of `CdkDrag`.
1785
+ * Event options that can be used to bind an active, capturing event.
1519
1786
  * @type {?}
1520
1787
  */
1521
- const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG', {
1522
- providedIn: 'root',
1523
- factory: CDK_DRAG_CONFIG_FACTORY
1788
+ const activeCapturingEventOptions = normalizePassiveListenerOptions({
1789
+ passive: false,
1790
+ capture: true
1524
1791
  });
1525
1792
  /**
1793
+ * Service that keeps track of all the drag item and drop container
1794
+ * instances, and manages global event listeners on the `document`.
1526
1795
  * \@docs-private
1527
- * @return {?}
1528
- */
1529
- function CDK_DRAG_CONFIG_FACTORY() {
1530
- return { dragStartThreshold: 5, pointerDirectionChangeThreshold: 5 };
1531
- }
1532
- /**
1533
- * Element that can be moved inside a CdkDropList container.
1534
- * @template T
1796
+ * @template I, C
1535
1797
  */
1536
- class CdkDrag {
1798
+ // Note: this class is generic, rather than referencing CdkDrag and CdkDropList directly, in order
1799
+ // to avoid circular imports. If we were to reference them here, importing the registry into the
1800
+ // classes that are registering themselves will introduce a circular import.
1801
+ class DragDropRegistry {
1537
1802
  /**
1538
- * @param {?} element
1539
- * @param {?} dropContainer
1540
- * @param {?} _document
1541
1803
  * @param {?} _ngZone
1542
- * @param {?} _viewContainerRef
1543
- * @param {?} _viewportRuler
1544
- * @param {?} _dragDropRegistry
1545
- * @param {?} _config
1546
- * @param {?} _dir
1804
+ * @param {?} _document
1547
1805
  */
1548
- constructor(element, dropContainer, _document, _ngZone, _viewContainerRef, _viewportRuler, _dragDropRegistry, _config, _dir) {
1549
- this.element = element;
1550
- this.dropContainer = dropContainer;
1551
- this._document = _document;
1806
+ constructor(_ngZone, _document) {
1552
1807
  this._ngZone = _ngZone;
1553
- this._viewContainerRef = _viewContainerRef;
1554
- this._viewportRuler = _viewportRuler;
1555
- this._dragDropRegistry = _dragDropRegistry;
1556
- this._config = _config;
1557
- this._dir = _dir;
1558
- this._destroyed = new Subject();
1559
- this._disabled = false;
1560
1808
  /**
1561
- * Emits when the user starts dragging the item.
1809
+ * Registered drop container instances.
1562
1810
  */
1563
- this.started = new EventEmitter();
1811
+ this._dropInstances = new Set();
1564
1812
  /**
1565
- * Emits when the user has released a drag item, before any animations have started.
1813
+ * Registered drag item instances.
1566
1814
  */
1567
- this.released = new EventEmitter();
1815
+ this._dragInstances = new Set();
1568
1816
  /**
1569
- * Emits when the user stops dragging an item in the container.
1817
+ * Drag item instances that are currently being dragged.
1570
1818
  */
1571
- this.ended = new EventEmitter();
1819
+ this._activeDragInstances = new Set();
1572
1820
  /**
1573
- * Emits when the user has moved the item into a new container.
1821
+ * Keeps track of the event listeners that we've bound to the `document`.
1574
1822
  */
1575
- this.entered = new EventEmitter();
1823
+ this._globalListeners = new Map();
1576
1824
  /**
1577
- * Emits when the user removes the item its container by dragging it into another container.
1825
+ * Emits the `touchmove` or `mousemove` events that are dispatched
1826
+ * while the user is dragging a drag item instance.
1578
1827
  */
1579
- this.exited = new EventEmitter();
1828
+ this.pointerMove = new Subject();
1580
1829
  /**
1581
- * Emits when the user drops the item inside a container.
1830
+ * Emits the `touchend` or `mouseup` events that are dispatched
1831
+ * while the user is dragging a drag item instance.
1582
1832
  */
1583
- this.dropped = new EventEmitter();
1833
+ this.pointerUp = new Subject();
1584
1834
  /**
1585
- * Emits as the user is dragging the item. Use with caution,
1586
- * because this event will fire for every pixel that the user has dragged.
1835
+ * Event listener that will prevent the default browser action while the user is dragging.
1836
+ * @param event Event whose default action should be prevented.
1587
1837
  */
1588
- this.moved = new Observable((observer) => {
1589
- /** @type {?} */
1590
- const subscription = this._dragRef.moved.pipe(map(movedEvent => ({
1591
- source: this,
1592
- pointerPosition: movedEvent.pointerPosition,
1593
- event: movedEvent.event,
1594
- delta: movedEvent.delta
1595
- }))).subscribe(observer);
1596
- return () => {
1597
- subscription.unsubscribe();
1598
- };
1599
- });
1600
- /** @type {?} */
1601
- const ref = this._dragRef = new DragRef(element, this._document, this._ngZone, this._viewContainerRef, this._viewportRuler, this._dragDropRegistry, this._config, this.dropContainer ? this.dropContainer._dropListRef : undefined, this._dir);
1602
- ref.data = this;
1603
- this._syncInputs(ref);
1604
- this._proxyEvents(ref);
1605
- }
1606
- /**
1607
- * Whether starting to drag this element is disabled.
1608
- * @return {?}
1609
- */
1610
- get disabled() {
1611
- return this._disabled || (this.dropContainer && this.dropContainer.disabled);
1612
- }
1613
- /**
1614
- * @param {?} value
1615
- * @return {?}
1616
- */
1617
- set disabled(value) {
1618
- this._disabled = coerceBooleanProperty(value);
1619
- this._dragRef.disabled = this._disabled;
1838
+ this._preventDefaultWhileDragging = (event) => {
1839
+ if (this._activeDragInstances.size) {
1840
+ event.preventDefault();
1841
+ }
1842
+ };
1843
+ this._document = _document;
1620
1844
  }
1621
1845
  /**
1622
- * Returns the element that is being used as a placeholder
1623
- * while the current element is being dragged.
1846
+ * Adds a drop container to the registry.
1847
+ * @param {?} drop
1624
1848
  * @return {?}
1625
1849
  */
1626
- getPlaceholderElement() {
1627
- return this._dragRef.getPlaceholderElement();
1850
+ registerDropContainer(drop) {
1851
+ if (!this._dropInstances.has(drop)) {
1852
+ if (this.getDropContainer(drop.id)) {
1853
+ throw Error(`Drop instance with id "${drop.id}" has already been registered.`);
1854
+ }
1855
+ this._dropInstances.add(drop);
1856
+ }
1628
1857
  }
1629
1858
  /**
1630
- * Returns the root draggable element.
1859
+ * Adds a drag item instance to the registry.
1860
+ * @param {?} drag
1631
1861
  * @return {?}
1632
1862
  */
1633
- getRootElement() {
1634
- return this._dragRef.getRootElement();
1863
+ registerDragItem(drag) {
1864
+ this._dragInstances.add(drag);
1865
+ // The `touchmove` event gets bound once, ahead of time, because WebKit
1866
+ // won't preventDefault on a dynamically-added `touchmove` listener.
1867
+ // See https://bugs.webkit.org/show_bug.cgi?id=184250.
1868
+ if (this._dragInstances.size === 1) {
1869
+ this._ngZone.runOutsideAngular(() => {
1870
+ // The event handler has to be explicitly active,
1871
+ // because newer browsers make it passive by default.
1872
+ this._document.addEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions);
1873
+ });
1874
+ }
1635
1875
  }
1636
1876
  /**
1637
- * Resets a standalone drag item to its initial position.
1877
+ * Removes a drop container from the registry.
1878
+ * @param {?} drop
1638
1879
  * @return {?}
1639
1880
  */
1640
- reset() {
1641
- this._dragRef.reset();
1881
+ removeDropContainer(drop) {
1882
+ this._dropInstances.delete(drop);
1642
1883
  }
1643
1884
  /**
1885
+ * Removes a drag item instance from the registry.
1886
+ * @param {?} drag
1644
1887
  * @return {?}
1645
1888
  */
1646
- ngAfterViewInit() {
1647
- // We need to wait for the zone to stabilize, in order for the reference
1648
- // element to be in the proper place in the DOM. This is mostly relevant
1649
- // for draggable elements inside portals since they get stamped out in
1650
- // their original DOM position and then they get transferred to the portal.
1651
- this._ngZone.onStable.asObservable()
1652
- .pipe(take(1), takeUntil(this._destroyed))
1653
- .subscribe(() => {
1654
- this._updateRootElement();
1655
- // Listen for any newly-added handles.
1656
- this._handles.changes.pipe(startWith(this._handles),
1657
- // Sync the new handles with the DragRef.
1658
- tap((handles) => {
1659
- /** @type {?} */
1660
- const childHandleElements = handles
1661
- .filter(handle => handle._parentDrag === this)
1662
- .map(handle => handle.element);
1663
- this._dragRef.withHandles(childHandleElements);
1664
- }),
1665
- // Listen if the state of any of the handles changes.
1666
- switchMap((handles) => {
1667
- return merge(...handles.map(item => item._stateChanges));
1668
- }), takeUntil(this._destroyed)).subscribe(handleInstance => {
1669
- // Enabled/disable the handle that changed in the DragRef.
1670
- /** @type {?} */
1671
- const dragRef = this._dragRef;
1672
- /** @type {?} */
1673
- const handle = handleInstance.element.nativeElement;
1674
- handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle);
1675
- });
1676
- });
1889
+ removeDragItem(drag) {
1890
+ this._dragInstances.delete(drag);
1891
+ this.stopDragging(drag);
1892
+ if (this._dragInstances.size === 0) {
1893
+ this._document.removeEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions);
1894
+ }
1677
1895
  }
1678
1896
  /**
1679
- * @param {?} changes
1897
+ * Starts the dragging sequence for a drag instance.
1898
+ * @param {?} drag Drag instance which is being dragged.
1899
+ * @param {?} event Event that initiated the dragging.
1680
1900
  * @return {?}
1681
1901
  */
1682
- ngOnChanges(changes) {
1683
- /** @type {?} */
1684
- const rootSelectorChange = changes.rootElementSelector;
1685
- // We don't have to react to the first change since it's being
1686
- // handled in `ngAfterViewInit` where it needs to be deferred.
1687
- if (rootSelectorChange && !rootSelectorChange.firstChange) {
1688
- this._updateRootElement();
1902
+ startDragging(drag, event) {
1903
+ this._activeDragInstances.add(drag);
1904
+ if (this._activeDragInstances.size === 1) {
1905
+ /** @type {?} */
1906
+ const isTouchEvent = event.type.startsWith('touch');
1907
+ /** @type {?} */
1908
+ const moveEvent = isTouchEvent ? 'touchmove' : 'mousemove';
1909
+ /** @type {?} */
1910
+ const upEvent = isTouchEvent ? 'touchend' : 'mouseup';
1911
+ // We explicitly bind __active__ listeners here, because newer browsers will default to
1912
+ // passive ones for `mousemove` and `touchmove`. The events need to be active, because we
1913
+ // use `preventDefault` to prevent the page from scrolling while the user is dragging.
1914
+ this._globalListeners
1915
+ .set(moveEvent, {
1916
+ handler: (e) => this.pointerMove.next((/** @type {?} */ (e))),
1917
+ options: activeCapturingEventOptions
1918
+ })
1919
+ .set(upEvent, {
1920
+ handler: (e) => this.pointerUp.next((/** @type {?} */ (e))),
1921
+ options: true
1922
+ })
1923
+ // Preventing the default action on `mousemove` isn't enough to disable text selection
1924
+ // on Safari so we need to prevent the selection event as well. Alternatively this can
1925
+ // be done by setting `user-select: none` on the `body`, however it has causes a style
1926
+ // recalculation which can be expensive on pages with a lot of elements.
1927
+ .set('selectstart', {
1928
+ handler: this._preventDefaultWhileDragging,
1929
+ options: activeCapturingEventOptions
1930
+ });
1931
+ // TODO(crisbeto): prevent mouse wheel scrolling while
1932
+ // dragging until we've set up proper scroll handling.
1933
+ if (!isTouchEvent) {
1934
+ this._globalListeners.set('wheel', {
1935
+ handler: this._preventDefaultWhileDragging,
1936
+ options: activeCapturingEventOptions
1937
+ });
1938
+ }
1939
+ this._ngZone.runOutsideAngular(() => {
1940
+ this._globalListeners.forEach((config, name) => {
1941
+ this._document.addEventListener(name, config.handler, config.options);
1942
+ });
1943
+ });
1689
1944
  }
1690
1945
  }
1691
1946
  /**
1947
+ * Stops dragging a drag item instance.
1948
+ * @param {?} drag
1692
1949
  * @return {?}
1693
1950
  */
1694
- ngOnDestroy() {
1695
- this._destroyed.next();
1696
- this._destroyed.complete();
1697
- this._dragRef.dispose();
1951
+ stopDragging(drag) {
1952
+ this._activeDragInstances.delete(drag);
1953
+ if (this._activeDragInstances.size === 0) {
1954
+ this._clearGlobalListeners();
1955
+ }
1698
1956
  }
1699
1957
  /**
1700
- * Syncs the root element with the `DragRef`.
1701
- * @private
1958
+ * Gets whether a drag item instance is currently being dragged.
1959
+ * @param {?} drag
1702
1960
  * @return {?}
1703
1961
  */
1704
- _updateRootElement() {
1705
- /** @type {?} */
1706
- const element = this.element.nativeElement;
1707
- /** @type {?} */
1708
- const rootElement = this.rootElementSelector ?
1709
- getClosestMatchingAncestor(element, this.rootElementSelector) : element;
1710
- if (rootElement && rootElement.nodeType !== this._document.ELEMENT_NODE) {
1711
- throw Error(`cdkDrag must be attached to an element node. ` +
1712
- `Currently attached to "${rootElement.nodeName}".`);
1713
- }
1714
- this._dragRef.withRootElement(rootElement || element);
1962
+ isDragging(drag) {
1963
+ return this._activeDragInstances.has(drag);
1715
1964
  }
1716
1965
  /**
1717
- * Gets the boundary element, based on the `boundaryElementSelector`.
1718
- * @private
1966
+ * Gets a drop container by its id.
1967
+ * @deprecated No longer being used. To be removed.
1968
+ * \@breaking-change 8.0.0
1969
+ * @param {?} id
1719
1970
  * @return {?}
1720
1971
  */
1721
- _getBoundaryElement() {
1722
- /** @type {?} */
1723
- const selector = this.boundaryElementSelector;
1724
- return selector ? getClosestMatchingAncestor(this.element.nativeElement, selector) : null;
1972
+ getDropContainer(id) {
1973
+ return Array.from(this._dropInstances).find(instance => instance.id === id);
1725
1974
  }
1726
1975
  /**
1727
- * Syncs the inputs of the CdkDrag with the options of the underlying DragRef.
1728
- * @private
1729
- * @param {?} ref
1730
1976
  * @return {?}
1731
1977
  */
1732
- _syncInputs(ref) {
1733
- ref.beforeStarted.subscribe(() => {
1734
- if (!ref.isDragging()) {
1735
- const { _placeholderTemplate: placeholder, _previewTemplate: preview } = this;
1736
- ref.disabled = this.disabled;
1737
- ref.lockAxis = this.lockAxis;
1738
- ref.withBoundaryElement(this._getBoundaryElement());
1739
- placeholder ? ref.withPlaceholderTemplate(placeholder.templateRef, placeholder.data) :
1740
- ref.withPlaceholderTemplate(null);
1741
- preview ? ref.withPreviewTemplate(preview.templateRef, preview.data) :
1742
- ref.withPreviewTemplate(null);
1743
- }
1744
- });
1978
+ ngOnDestroy() {
1979
+ this._dragInstances.forEach(instance => this.removeDragItem(instance));
1980
+ this._dropInstances.forEach(instance => this.removeDropContainer(instance));
1981
+ this._clearGlobalListeners();
1982
+ this.pointerMove.complete();
1983
+ this.pointerUp.complete();
1745
1984
  }
1746
1985
  /**
1747
- * Proxies the events from a DragRef to events that
1748
- * match the interfaces of the CdkDrag outputs.
1986
+ * Clears out the global event listeners from the `document`.
1749
1987
  * @private
1750
- * @param {?} ref
1751
1988
  * @return {?}
1752
1989
  */
1753
- _proxyEvents(ref) {
1754
- ref.started.subscribe(() => {
1755
- this.started.emit({ source: this });
1756
- });
1757
- ref.released.subscribe(() => {
1758
- this.released.emit({ source: this });
1759
- });
1760
- ref.ended.subscribe(() => {
1761
- this.ended.emit({ source: this });
1762
- });
1763
- ref.entered.subscribe(event => {
1764
- this.entered.emit({
1765
- container: event.container.data,
1766
- item: this
1767
- });
1768
- });
1769
- ref.exited.subscribe(event => {
1770
- this.exited.emit({
1771
- container: event.container.data,
1772
- item: this
1773
- });
1774
- });
1775
- ref.dropped.subscribe(event => {
1776
- this.dropped.emit({
1777
- previousIndex: event.previousIndex,
1778
- currentIndex: event.currentIndex,
1779
- previousContainer: event.previousContainer.data,
1780
- container: event.container.data,
1781
- isPointerOverContainer: event.isPointerOverContainer,
1782
- item: this
1783
- });
1990
+ _clearGlobalListeners() {
1991
+ this._globalListeners.forEach((config, name) => {
1992
+ this._document.removeEventListener(name, config.handler, config.options);
1784
1993
  });
1994
+ this._globalListeners.clear();
1785
1995
  }
1786
1996
  }
1787
- CdkDrag.decorators = [
1788
- { type: Directive, args: [{
1789
- selector: '[cdkDrag]',
1790
- exportAs: 'cdkDrag',
1791
- host: {
1792
- 'class': 'cdk-drag',
1793
- '[class.cdk-drag-dragging]': '_dragRef.isDragging()',
1794
- },
1795
- providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }]
1796
- },] },
1997
+ DragDropRegistry.decorators = [
1998
+ { type: Injectable, args: [{ providedIn: 'root' },] },
1797
1999
  ];
1798
2000
  /** @nocollapse */
1799
- CdkDrag.ctorParameters = () => [
1800
- { type: ElementRef },
1801
- { type: undefined, decorators: [{ type: Inject, args: [CDK_DROP_LIST,] }, { type: Optional }, { type: SkipSelf }] },
1802
- { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2001
+ DragDropRegistry.ctorParameters = () => [
1803
2002
  { type: NgZone },
1804
- { type: ViewContainerRef },
1805
- { type: ViewportRuler },
1806
- { type: DragDropRegistry },
1807
- { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_CONFIG,] }] },
1808
- { type: Directionality, decorators: [{ type: Optional }] }
2003
+ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
1809
2004
  ];
1810
- CdkDrag.propDecorators = {
1811
- _handles: [{ type: ContentChildren, args: [CdkDragHandle, { descendants: true },] }],
1812
- _previewTemplate: [{ type: ContentChild, args: [CdkDragPreview,] }],
1813
- _placeholderTemplate: [{ type: ContentChild, args: [CdkDragPlaceholder,] }],
1814
- data: [{ type: Input, args: ['cdkDragData',] }],
1815
- lockAxis: [{ type: Input, args: ['cdkDragLockAxis',] }],
1816
- rootElementSelector: [{ type: Input, args: ['cdkDragRootElement',] }],
1817
- boundaryElementSelector: [{ type: Input, args: ['cdkDragBoundary',] }],
1818
- disabled: [{ type: Input, args: ['cdkDragDisabled',] }],
1819
- started: [{ type: Output, args: ['cdkDragStarted',] }],
1820
- released: [{ type: Output, args: ['cdkDragReleased',] }],
1821
- ended: [{ type: Output, args: ['cdkDragEnded',] }],
1822
- entered: [{ type: Output, args: ['cdkDragEntered',] }],
1823
- exited: [{ type: Output, args: ['cdkDragExited',] }],
1824
- dropped: [{ type: Output, args: ['cdkDragDropped',] }],
1825
- moved: [{ type: Output, args: ['cdkDragMoved',] }]
1826
- };
1827
- /**
1828
- * Gets the closest ancestor of an element that matches a selector.
1829
- * @param {?} element
1830
- * @param {?} selector
1831
- * @return {?}
1832
- */
1833
- function getClosestMatchingAncestor(element, selector) {
1834
- /** @type {?} */
1835
- let currentElement = (/** @type {?} */ (element.parentElement));
1836
- while (currentElement) {
1837
- // IE doesn't support `matches` so we have to fall back to `msMatchesSelector`.
1838
- if (currentElement.matches ? currentElement.matches(selector) :
1839
- ((/** @type {?} */ (currentElement))).msMatchesSelector(selector)) {
1840
- return currentElement;
1841
- }
1842
- currentElement = currentElement.parentElement;
1843
- }
1844
- return null;
1845
- }
2005
+ /** @nocollapse */ DragDropRegistry.ngInjectableDef = defineInjectable({ factory: function DragDropRegistry_Factory() { return new DragDropRegistry(inject(NgZone), inject(DOCUMENT)); }, token: DragDropRegistry, providedIn: "root" });
1846
2006
 
1847
2007
  /**
1848
2008
  * @fileoverview added by tsickle
1849
2009
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
1850
2010
  */
1851
2011
  /**
1852
- * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`
1853
- * elements that are placed inside a `cdkDropListGroup` will be connected to each other
1854
- * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input
1855
- * from `cdkDropList`.
1856
- * @template T
2012
+ * Default configuration to be used when creating a `DragRef`.
2013
+ * @type {?}
1857
2014
  */
1858
- class CdkDropListGroup {
1859
- constructor() {
1860
- /**
1861
- * Drop lists registered inside the group.
1862
- */
1863
- this._items = new Set();
1864
- this._disabled = false;
1865
- }
2015
+ const DEFAULT_CONFIG = {
2016
+ dragStartThreshold: 5,
2017
+ pointerDirectionChangeThreshold: 5
2018
+ };
2019
+ /**
2020
+ * Service that allows for drag-and-drop functionality to be attached to DOM elements.
2021
+ */
2022
+ class DragDrop {
1866
2023
  /**
1867
- * Whether starting a dragging sequence from inside this group is disabled.
1868
- * @return {?}
2024
+ * @param {?} _document
2025
+ * @param {?} _ngZone
2026
+ * @param {?} _viewportRuler
2027
+ * @param {?} _dragDropRegistry
1869
2028
  */
1870
- get disabled() { return this._disabled; }
2029
+ constructor(_document, _ngZone, _viewportRuler, _dragDropRegistry) {
2030
+ this._document = _document;
2031
+ this._ngZone = _ngZone;
2032
+ this._viewportRuler = _viewportRuler;
2033
+ this._dragDropRegistry = _dragDropRegistry;
2034
+ }
1871
2035
  /**
1872
- * @param {?} value
2036
+ * Turns an element into a draggable item.
2037
+ * @template T
2038
+ * @param {?} element Element to which to attach the dragging functionality.
2039
+ * @param {?=} config Object used to configure the dragging behavior.
1873
2040
  * @return {?}
1874
2041
  */
1875
- set disabled(value) {
1876
- this._disabled = coerceBooleanProperty(value);
2042
+ createDrag(element, config = DEFAULT_CONFIG) {
2043
+ return new DragRef(element, config, this._document, this._ngZone, this._viewportRuler, this._dragDropRegistry);
1877
2044
  }
1878
2045
  /**
2046
+ * Turns an element into a drop list.
2047
+ * @template T
2048
+ * @param {?} element Element to which to attach the drop list functionality.
1879
2049
  * @return {?}
1880
2050
  */
1881
- ngOnDestroy() {
1882
- this._items.clear();
2051
+ createDropList(element) {
2052
+ return new DropListRef(element, this._dragDropRegistry, this._document);
1883
2053
  }
1884
2054
  }
1885
- CdkDropListGroup.decorators = [
1886
- { type: Directive, args: [{
1887
- selector: '[cdkDropListGroup]',
1888
- exportAs: 'cdkDropListGroup',
1889
- },] },
2055
+ DragDrop.decorators = [
2056
+ { type: Injectable, args: [{ providedIn: 'root' },] },
1890
2057
  ];
1891
- CdkDropListGroup.propDecorators = {
1892
- disabled: [{ type: Input, args: ['cdkDropListGroupDisabled',] }]
1893
- };
2058
+ /** @nocollapse */
2059
+ DragDrop.ctorParameters = () => [
2060
+ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2061
+ { type: NgZone },
2062
+ { type: ViewportRuler },
2063
+ { type: DragDropRegistry }
2064
+ ];
2065
+ /** @nocollapse */ DragDrop.ngInjectableDef = defineInjectable({ factory: function DragDrop_Factory() { return new DragDrop(inject(DOCUMENT), inject(NgZone), inject(ViewportRuler), inject(DragDropRegistry)); }, token: DragDrop, providedIn: "root" });
1894
2066
 
1895
2067
  /**
1896
2068
  * @fileoverview added by tsickle
1897
2069
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
1898
2070
  */
1899
2071
  /**
1900
- * Counter used to generate unique ids for drop refs.
2072
+ * Injection token that is used to provide a CdkDropList instance to CdkDrag.
2073
+ * Used for avoiding circular imports.
1901
2074
  * @type {?}
1902
2075
  */
1903
- let _uniqueIdCounter = 0;
2076
+ const CDK_DROP_LIST = new InjectionToken('CDK_DROP_LIST');
1904
2077
  /**
1905
- * Proximity, as a ratio to width/height, at which a
1906
- * dragged item will affect the drop container.
2078
+ * Injection token that is used to provide a CdkDropList instance to CdkDrag.
2079
+ * Used for avoiding circular imports.
2080
+ * @deprecated Use `CDK_DROP_LIST` instead.
2081
+ * \@breaking-change 8.0.0
1907
2082
  * @type {?}
1908
2083
  */
1909
- const DROP_PROXIMITY_THRESHOLD = 0.05;
2084
+ const CDK_DROP_LIST_CONTAINER = CDK_DROP_LIST;
2085
+
1910
2086
  /**
1911
- * Reference to a drop list. Used to manipulate or dispose of the container.
2087
+ * @fileoverview added by tsickle
2088
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2089
+ */
2090
+
2091
+ /**
2092
+ * @fileoverview added by tsickle
2093
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2094
+ */
2095
+ /**
2096
+ * Injection token that can be used for a `CdkDrag` to provide itself as a parent to the
2097
+ * drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily
2098
+ * to avoid circular imports.
1912
2099
  * \@docs-private
1913
- * @template T
2100
+ * @type {?}
1914
2101
  */
1915
- class DropListRef {
1916
- /**
1917
- * @param {?} element
1918
- * @param {?} _dragDropRegistry
1919
- * @param {?} _document
1920
- * @param {?=} _dir
1921
- */
1922
- constructor(element, _dragDropRegistry, _document, _dir) {
1923
- this.element = element;
1924
- this._dragDropRegistry = _dragDropRegistry;
1925
- this._dir = _dir;
1926
- /**
1927
- * Unique ID for the drop list.
1928
- * @deprecated No longer being used. To be removed.
1929
- * \@breaking-change 8.0.0
1930
- */
1931
- this.id = `cdk-drop-list-ref-${_uniqueIdCounter++}`;
1932
- /**
1933
- * Whether starting a dragging sequence from this container is disabled.
1934
- */
1935
- this.disabled = false;
1936
- /**
1937
- * Function that is used to determine whether an item
1938
- * is allowed to be moved into a drop container.
1939
- */
1940
- this.enterPredicate = () => true;
1941
- /**
1942
- * Emits right before dragging has started.
1943
- */
1944
- this.beforeStarted = new Subject();
1945
- /**
1946
- * Emits when the user has moved a new drag item into this container.
1947
- */
1948
- this.entered = new Subject();
1949
- /**
1950
- * Emits when the user removes an item from the container
1951
- * by dragging it into another container.
1952
- */
1953
- this.exited = new Subject();
1954
- /**
1955
- * Emits when the user drops an item inside the container.
1956
- */
1957
- this.dropped = new Subject();
1958
- /**
1959
- * Emits as the user is swapping items while actively dragging.
1960
- */
1961
- this.sorted = new Subject();
1962
- /**
1963
- * Whether an item in the list is being dragged.
1964
- */
1965
- this._isDragging = false;
1966
- /**
1967
- * Cache of the dimensions of all the items inside the container.
1968
- */
1969
- this._itemPositions = [];
1970
- /**
1971
- * Keeps track of the item that was last swapped with the dragged item, as
1972
- * well as what direction the pointer was moving in when the swap occured.
1973
- */
1974
- this._previousSwap = { drag: (/** @type {?} */ (null)), delta: 0 };
1975
- /**
1976
- * Drop lists that are connected to the current one.
1977
- */
1978
- this._siblings = [];
1979
- /**
1980
- * Direction in which the list is oriented.
1981
- */
1982
- this._orientation = 'vertical';
1983
- /**
1984
- * Connected siblings that currently have a dragged item.
1985
- */
1986
- this._activeSiblings = new Set();
1987
- _dragDropRegistry.registerDropContainer(this);
1988
- this._document = _document;
1989
- }
1990
- /**
1991
- * Removes the drop list functionality from the DOM element.
1992
- * @return {?}
1993
- */
1994
- dispose() {
1995
- this.beforeStarted.complete();
1996
- this.entered.complete();
1997
- this.exited.complete();
1998
- this.dropped.complete();
1999
- this.sorted.complete();
2000
- this._activeSiblings.clear();
2001
- this._dragDropRegistry.removeDropContainer(this);
2002
- }
2003
- /**
2004
- * Whether an item from this list is currently being dragged.
2005
- * @return {?}
2006
- */
2007
- isDragging() {
2008
- return this._isDragging;
2009
- }
2010
- /**
2011
- * Starts dragging an item.
2012
- * @return {?}
2013
- */
2014
- start() {
2015
- this.beforeStarted.next();
2016
- this._isDragging = true;
2017
- this._activeDraggables = this._draggables.slice();
2018
- this._cacheOwnPosition();
2019
- this._cacheItemPositions();
2020
- this._siblings.forEach(sibling => sibling._startReceiving(this));
2021
- }
2022
- /**
2023
- * Emits an event to indicate that the user moved an item into the container.
2024
- * @param {?} item Item that was moved into the container.
2025
- * @param {?} pointerX Position of the item along the X axis.
2026
- * @param {?} pointerY Position of the item along the Y axis.
2027
- * @return {?}
2028
- */
2029
- enter(item, pointerX, pointerY) {
2030
- this.entered.next({ item, container: this });
2031
- this.start();
2032
- // We use the coordinates of where the item entered the drop
2033
- // zone to figure out at which index it should be inserted.
2034
- /** @type {?} */
2035
- const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
2036
- /** @type {?} */
2037
- const currentIndex = this._activeDraggables.indexOf(item);
2038
- /** @type {?} */
2039
- const newPositionReference = this._activeDraggables[newIndex];
2040
- /** @type {?} */
2041
- const placeholder = item.getPlaceholderElement();
2042
- // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
2043
- // into another container and back again), we have to ensure that it isn't duplicated.
2044
- if (currentIndex > -1) {
2045
- this._activeDraggables.splice(currentIndex, 1);
2046
- }
2047
- // Don't use items that are being dragged as a reference, because
2048
- // their element has been moved down to the bottom of the body.
2049
- if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
2050
- /** @type {?} */
2051
- const element = newPositionReference.getRootElement();
2052
- (/** @type {?} */ (element.parentElement)).insertBefore(placeholder, element);
2053
- this._activeDraggables.splice(newIndex, 0, item);
2054
- }
2055
- else {
2056
- this.element.nativeElement.appendChild(placeholder);
2057
- this._activeDraggables.push(item);
2058
- }
2059
- // The transform needs to be cleared so it doesn't throw off the measurements.
2060
- placeholder.style.transform = '';
2061
- // Note that the positions were already cached when we called `start` above,
2062
- // but we need to refresh them since the amount of items has changed.
2063
- this._cacheItemPositions();
2064
- }
2065
- /**
2066
- * Removes an item from the container after it was dragged into another container by the user.
2067
- * @param {?} item Item that was dragged out.
2068
- * @return {?}
2069
- */
2070
- exit(item) {
2071
- this._reset();
2072
- this.exited.next({ item, container: this });
2073
- }
2074
- /**
2075
- * Drops an item into this container.
2076
- * @param {?} item Item being dropped into the container.
2077
- * @param {?} currentIndex Index at which the item should be inserted.
2078
- * @param {?} previousContainer Container from which the item got dragged in.
2079
- * @param {?} isPointerOverContainer Whether the user's pointer was over the
2080
- * container when the item was dropped.
2081
- * @return {?}
2082
- */
2083
- drop(item, currentIndex, previousContainer, isPointerOverContainer) {
2084
- this._reset();
2085
- this.dropped.next({
2086
- item,
2087
- currentIndex,
2088
- previousIndex: previousContainer.getItemIndex(item),
2089
- container: this,
2090
- previousContainer,
2091
- isPointerOverContainer
2092
- });
2093
- }
2102
+ const CDK_DRAG_PARENT = new InjectionToken('CDK_DRAG_PARENT');
2103
+
2104
+ /**
2105
+ * @fileoverview added by tsickle
2106
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2107
+ */
2108
+ /**
2109
+ * Handle that can be used to drag and CdkDrag instance.
2110
+ */
2111
+ class CdkDragHandle {
2094
2112
  /**
2095
- * Sets the draggable items that are a part of this list.
2096
- * @template THIS
2097
- * @this {THIS}
2098
- * @param {?} items Items that are a part of this list.
2099
- * @return {THIS}
2113
+ * @param {?} element
2114
+ * @param {?=} parentDrag
2100
2115
  */
2101
- withItems(items) {
2102
- (/** @type {?} */ (this))._draggables = items.slice();
2103
- return (/** @type {?} */ (this));
2116
+ constructor(element, parentDrag) {
2117
+ this.element = element;
2118
+ /**
2119
+ * Emits when the state of the handle has changed.
2120
+ */
2121
+ this._stateChanges = new Subject();
2122
+ this._disabled = false;
2123
+ this._parentDrag = parentDrag;
2124
+ toggleNativeDragInteractions(element.nativeElement, false);
2104
2125
  }
2105
2126
  /**
2106
- * Sets the containers that are connected to this one. When two or more containers are
2107
- * connected, the user will be allowed to transfer items between them.
2108
- * @template THIS
2109
- * @this {THIS}
2110
- * @param {?} connectedTo Other containers that the current containers should be connected to.
2111
- * @return {THIS}
2127
+ * Whether starting to drag through this handle is disabled.
2128
+ * @return {?}
2112
2129
  */
2113
- connectedTo(connectedTo) {
2114
- (/** @type {?} */ (this))._siblings = connectedTo.slice();
2115
- return (/** @type {?} */ (this));
2116
- }
2130
+ get disabled() { return this._disabled; }
2117
2131
  /**
2118
- * Sets the orientation of the container.
2119
- * @template THIS
2120
- * @this {THIS}
2121
- * @param {?} orientation New orientation for the container.
2122
- * @return {THIS}
2132
+ * @param {?} value
2133
+ * @return {?}
2123
2134
  */
2124
- withOrientation(orientation) {
2125
- (/** @type {?} */ (this))._orientation = orientation;
2126
- return (/** @type {?} */ (this));
2135
+ set disabled(value) {
2136
+ this._disabled = coerceBooleanProperty(value);
2137
+ this._stateChanges.next(this);
2127
2138
  }
2128
2139
  /**
2129
- * Figures out the index of an item in the container.
2130
- * @param {?} item Item whose index should be determined.
2131
2140
  * @return {?}
2132
2141
  */
2133
- getItemIndex(item) {
2134
- if (!this._isDragging) {
2135
- return this._draggables.indexOf(item);
2136
- }
2137
- // Items are sorted always by top/left in the cache, however they flow differently in RTL.
2138
- // The rest of the logic still stands no matter what orientation we're in, however
2139
- // we need to invert the array when determining the index.
2140
- /** @type {?} */
2141
- const items = this._orientation === 'horizontal' && this._dir && this._dir.value === 'rtl' ?
2142
- this._itemPositions.slice().reverse() : this._itemPositions;
2143
- return findIndex(items, currentItem => currentItem.drag === item);
2142
+ ngOnDestroy() {
2143
+ this._stateChanges.complete();
2144
2144
  }
2145
+ }
2146
+ CdkDragHandle.decorators = [
2147
+ { type: Directive, args: [{
2148
+ selector: '[cdkDragHandle]',
2149
+ host: {
2150
+ 'class': 'cdk-drag-handle'
2151
+ }
2152
+ },] },
2153
+ ];
2154
+ /** @nocollapse */
2155
+ CdkDragHandle.ctorParameters = () => [
2156
+ { type: ElementRef },
2157
+ { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_PARENT,] }, { type: Optional }] }
2158
+ ];
2159
+ CdkDragHandle.propDecorators = {
2160
+ disabled: [{ type: Input, args: ['cdkDragHandleDisabled',] }]
2161
+ };
2162
+
2163
+ /**
2164
+ * @fileoverview added by tsickle
2165
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2166
+ */
2167
+ /**
2168
+ * Element that will be used as a template for the placeholder of a CdkDrag when
2169
+ * it is being dragged. The placeholder is displayed in place of the element being dragged.
2170
+ * @template T
2171
+ */
2172
+ class CdkDragPlaceholder {
2145
2173
  /**
2146
- * Whether the list is able to receive the item that
2147
- * is currently being dragged inside a connected drop list.
2148
- * @return {?}
2174
+ * @param {?} templateRef
2149
2175
  */
2150
- isReceiving() {
2151
- return this._activeSiblings.size > 0;
2176
+ constructor(templateRef) {
2177
+ this.templateRef = templateRef;
2152
2178
  }
2179
+ }
2180
+ CdkDragPlaceholder.decorators = [
2181
+ { type: Directive, args: [{
2182
+ selector: 'ng-template[cdkDragPlaceholder]'
2183
+ },] },
2184
+ ];
2185
+ /** @nocollapse */
2186
+ CdkDragPlaceholder.ctorParameters = () => [
2187
+ { type: TemplateRef }
2188
+ ];
2189
+ CdkDragPlaceholder.propDecorators = {
2190
+ data: [{ type: Input }]
2191
+ };
2192
+
2193
+ /**
2194
+ * @fileoverview added by tsickle
2195
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2196
+ */
2197
+ /**
2198
+ * Element that will be used as a template for the preview
2199
+ * of a CdkDrag when it is being dragged.
2200
+ * @template T
2201
+ */
2202
+ class CdkDragPreview {
2153
2203
  /**
2154
- * Sorts an item inside the container based on its position.
2155
- * @param {?} item Item to be sorted.
2156
- * @param {?} pointerX Position of the item along the X axis.
2157
- * @param {?} pointerY Position of the item along the Y axis.
2158
- * @param {?} pointerDelta Direction in which the pointer is moving along each axis.
2159
- * @return {?}
2204
+ * @param {?} templateRef
2160
2205
  */
2161
- _sortItem(item, pointerX, pointerY, pointerDelta) {
2162
- // Don't sort the item if it's out of range.
2163
- if (!this._isPointerNearDropContainer(pointerX, pointerY)) {
2164
- return;
2165
- }
2166
- /** @type {?} */
2167
- const siblings = this._itemPositions;
2168
- /** @type {?} */
2169
- const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
2170
- if (newIndex === -1 && siblings.length > 0) {
2171
- return;
2172
- }
2173
- /** @type {?} */
2174
- const isHorizontal = this._orientation === 'horizontal';
2175
- /** @type {?} */
2176
- const currentIndex = findIndex(siblings, currentItem => currentItem.drag === item);
2177
- /** @type {?} */
2178
- const siblingAtNewPosition = siblings[newIndex];
2179
- /** @type {?} */
2180
- const currentPosition = siblings[currentIndex].clientRect;
2181
- /** @type {?} */
2182
- const newPosition = siblingAtNewPosition.clientRect;
2183
- /** @type {?} */
2184
- const delta = currentIndex > newIndex ? 1 : -1;
2185
- this._previousSwap.drag = siblingAtNewPosition.drag;
2186
- this._previousSwap.delta = isHorizontal ? pointerDelta.x : pointerDelta.y;
2187
- // How many pixels the item's placeholder should be offset.
2188
- /** @type {?} */
2189
- const itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta);
2190
- // How many pixels all the other items should be offset.
2191
- /** @type {?} */
2192
- const siblingOffset = this._getSiblingOffsetPx(currentIndex, siblings, delta);
2193
- // Save the previous order of the items before moving the item to its new index.
2194
- // We use this to check whether an item has been moved as a result of the sorting.
2195
- /** @type {?} */
2196
- const oldOrder = siblings.slice();
2197
- // Shuffle the array in place.
2198
- moveItemInArray(siblings, currentIndex, newIndex);
2199
- this.sorted.next({
2200
- previousIndex: currentIndex,
2201
- currentIndex: newIndex,
2202
- container: this,
2203
- item
2204
- });
2205
- siblings.forEach((sibling, index) => {
2206
- // Don't do anything if the position hasn't changed.
2207
- if (oldOrder[index] === sibling) {
2208
- return;
2209
- }
2210
- /** @type {?} */
2211
- const isDraggedItem = sibling.drag === item;
2212
- /** @type {?} */
2213
- const offset = isDraggedItem ? itemOffset : siblingOffset;
2214
- /** @type {?} */
2215
- const elementToOffset = isDraggedItem ? item.getPlaceholderElement() :
2216
- sibling.drag.getRootElement();
2217
- // Update the offset to reflect the new position.
2218
- sibling.offset += offset;
2219
- // Since we're moving the items with a `transform`, we need to adjust their cached
2220
- // client rects to reflect their new position, as well as swap their positions in the cache.
2221
- // Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the
2222
- // elements may be mid-animation which will give us a wrong result.
2223
- if (isHorizontal) {
2224
- // Round the transforms since some browsers will
2225
- // blur the elements, for sub-pixel transforms.
2226
- elementToOffset.style.transform = `translate3d(${Math.round(sibling.offset)}px, 0, 0)`;
2227
- adjustClientRect(sibling.clientRect, 0, offset);
2228
- }
2229
- else {
2230
- elementToOffset.style.transform = `translate3d(0, ${Math.round(sibling.offset)}px, 0)`;
2231
- adjustClientRect(sibling.clientRect, offset, 0);
2232
- }
2233
- });
2206
+ constructor(templateRef) {
2207
+ this.templateRef = templateRef;
2234
2208
  }
2209
+ }
2210
+ CdkDragPreview.decorators = [
2211
+ { type: Directive, args: [{
2212
+ selector: 'ng-template[cdkDragPreview]'
2213
+ },] },
2214
+ ];
2215
+ /** @nocollapse */
2216
+ CdkDragPreview.ctorParameters = () => [
2217
+ { type: TemplateRef }
2218
+ ];
2219
+ CdkDragPreview.propDecorators = {
2220
+ data: [{ type: Input }]
2221
+ };
2222
+
2223
+ /**
2224
+ * @fileoverview added by tsickle
2225
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2226
+ */
2227
+ /**
2228
+ * Injection token that can be used to configure the behavior of `CdkDrag`.
2229
+ * @type {?}
2230
+ */
2231
+ const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG', {
2232
+ providedIn: 'root',
2233
+ factory: CDK_DRAG_CONFIG_FACTORY
2234
+ });
2235
+ /**
2236
+ * \@docs-private
2237
+ * @return {?}
2238
+ */
2239
+ function CDK_DRAG_CONFIG_FACTORY() {
2240
+ return { dragStartThreshold: 5, pointerDirectionChangeThreshold: 5 };
2241
+ }
2242
+ /**
2243
+ * Element that can be moved inside a CdkDropList container.
2244
+ * @template T
2245
+ */
2246
+ class CdkDrag {
2235
2247
  /**
2236
- * Caches the position of the drop list.
2237
- * @private
2238
- * @return {?}
2248
+ * @param {?} element
2249
+ * @param {?} dropContainer
2250
+ * @param {?} _document
2251
+ * @param {?} _ngZone
2252
+ * @param {?} _viewContainerRef
2253
+ * @param {?} viewportRuler
2254
+ * @param {?} dragDropRegistry
2255
+ * @param {?} config
2256
+ * @param {?} _dir
2257
+ * @param {?=} dragDrop
2239
2258
  */
2240
- _cacheOwnPosition() {
2241
- this._clientRect = this.element.nativeElement.getBoundingClientRect();
2242
- }
2259
+ constructor(element, dropContainer, _document, _ngZone, _viewContainerRef, viewportRuler, dragDropRegistry, config, _dir,
2243
2260
  /**
2244
- * Refreshes the position cache of the items and sibling containers.
2245
- * @private
2246
- * @return {?}
2261
+ * @deprecated `viewportRuler` and `dragDropRegistry` parameters
2262
+ * to be removed. Also `dragDrop` parameter to be made required.
2263
+ * @breaking-change 8.0.0.
2247
2264
  */
2248
- _cacheItemPositions() {
2249
- /** @type {?} */
2250
- const isHorizontal = this._orientation === 'horizontal';
2251
- this._itemPositions = this._activeDraggables.map(drag => {
2252
- /** @type {?} */
2253
- const elementToMeasure = this._dragDropRegistry.isDragging(drag) ?
2254
- // If the element is being dragged, we have to measure the
2255
- // placeholder, because the element is hidden.
2256
- drag.getPlaceholderElement() :
2257
- drag.getRootElement();
2265
+ dragDrop) {
2266
+ this.element = element;
2267
+ this.dropContainer = dropContainer;
2268
+ this._document = _document;
2269
+ this._ngZone = _ngZone;
2270
+ this._viewContainerRef = _viewContainerRef;
2271
+ this._dir = _dir;
2272
+ this._destroyed = new Subject();
2273
+ this._disabled = false;
2274
+ /**
2275
+ * Emits when the user starts dragging the item.
2276
+ */
2277
+ this.started = new EventEmitter();
2278
+ /**
2279
+ * Emits when the user has released a drag item, before any animations have started.
2280
+ */
2281
+ this.released = new EventEmitter();
2282
+ /**
2283
+ * Emits when the user stops dragging an item in the container.
2284
+ */
2285
+ this.ended = new EventEmitter();
2286
+ /**
2287
+ * Emits when the user has moved the item into a new container.
2288
+ */
2289
+ this.entered = new EventEmitter();
2290
+ /**
2291
+ * Emits when the user removes the item its container by dragging it into another container.
2292
+ */
2293
+ this.exited = new EventEmitter();
2294
+ /**
2295
+ * Emits when the user drops the item inside a container.
2296
+ */
2297
+ this.dropped = new EventEmitter();
2298
+ /**
2299
+ * Emits as the user is dragging the item. Use with caution,
2300
+ * because this event will fire for every pixel that the user has dragged.
2301
+ */
2302
+ this.moved = new Observable((observer) => {
2258
2303
  /** @type {?} */
2259
- const clientRect = elementToMeasure.getBoundingClientRect();
2260
- return {
2261
- drag,
2262
- offset: 0,
2263
- // We need to clone the `clientRect` here, because all the values on it are readonly
2264
- // and we need to be able to update them. Also we can't use a spread here, because
2265
- // the values on a `ClientRect` aren't own properties. See:
2266
- // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
2267
- clientRect: {
2268
- top: clientRect.top,
2269
- right: clientRect.right,
2270
- bottom: clientRect.bottom,
2271
- left: clientRect.left,
2272
- width: clientRect.width,
2273
- height: clientRect.height
2274
- }
2304
+ const subscription = this._dragRef.moved.pipe(map(movedEvent => ({
2305
+ source: this,
2306
+ pointerPosition: movedEvent.pointerPosition,
2307
+ event: movedEvent.event,
2308
+ delta: movedEvent.delta
2309
+ }))).subscribe(observer);
2310
+ return () => {
2311
+ subscription.unsubscribe();
2275
2312
  };
2276
- }).sort((a, b) => {
2277
- return isHorizontal ? a.clientRect.left - b.clientRect.left :
2278
- a.clientRect.top - b.clientRect.top;
2279
2313
  });
2314
+ // @breaking-change 8.0.0 Remove null check once the paramter is made required.
2315
+ if (dragDrop) {
2316
+ this._dragRef = dragDrop.createDrag(element, config);
2317
+ }
2318
+ else {
2319
+ this._dragRef = new DragRef(element, config, _document, _ngZone, viewportRuler, dragDropRegistry);
2320
+ }
2321
+ this._dragRef.data = this;
2322
+ this._syncInputs(this._dragRef);
2323
+ this._proxyEvents(this._dragRef);
2280
2324
  }
2281
2325
  /**
2282
- * Resets the container to its initial state.
2283
- * @private
2326
+ * Whether starting to drag this element is disabled.
2284
2327
  * @return {?}
2285
2328
  */
2286
- _reset() {
2287
- this._isDragging = false;
2288
- // TODO(crisbeto): may have to wait for the animations to finish.
2289
- this._activeDraggables.forEach(item => item.getRootElement().style.transform = '');
2290
- this._siblings.forEach(sibling => sibling._stopReceiving(this));
2291
- this._activeDraggables = [];
2292
- this._itemPositions = [];
2293
- this._previousSwap.drag = null;
2294
- this._previousSwap.delta = 0;
2329
+ get disabled() {
2330
+ return this._disabled || (this.dropContainer && this.dropContainer.disabled);
2295
2331
  }
2296
2332
  /**
2297
- * Gets the offset in pixels by which the items that aren't being dragged should be moved.
2298
- * @private
2299
- * @param {?} currentIndex Index of the item currently being dragged.
2300
- * @param {?} siblings All of the items in the list.
2301
- * @param {?} delta Direction in which the user is moving.
2333
+ * @param {?} value
2302
2334
  * @return {?}
2303
2335
  */
2304
- _getSiblingOffsetPx(currentIndex, siblings, delta) {
2305
- /** @type {?} */
2306
- const isHorizontal = this._orientation === 'horizontal';
2307
- /** @type {?} */
2308
- const currentPosition = siblings[currentIndex].clientRect;
2309
- /** @type {?} */
2310
- const immediateSibling = siblings[currentIndex + delta * -1];
2311
- /** @type {?} */
2312
- let siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta;
2313
- if (immediateSibling) {
2314
- /** @type {?} */
2315
- const start = isHorizontal ? 'left' : 'top';
2316
- /** @type {?} */
2317
- const end = isHorizontal ? 'right' : 'bottom';
2318
- // Get the spacing between the start of the current item and the end of the one immediately
2319
- // after it in the direction in which the user is dragging, or vice versa. We add it to the
2320
- // offset in order to push the element to where it will be when it's inline and is influenced
2321
- // by the `margin` of its siblings.
2322
- if (delta === -1) {
2323
- siblingOffset -= immediateSibling.clientRect[start] - currentPosition[end];
2324
- }
2325
- else {
2326
- siblingOffset += currentPosition[start] - immediateSibling.clientRect[end];
2327
- }
2328
- }
2329
- return siblingOffset;
2336
+ set disabled(value) {
2337
+ this._disabled = coerceBooleanProperty(value);
2338
+ this._dragRef.disabled = this._disabled;
2330
2339
  }
2331
2340
  /**
2332
- * Checks whether the pointer coordinates are close to the drop container.
2333
- * @private
2334
- * @param {?} pointerX Coordinates along the X axis.
2335
- * @param {?} pointerY Coordinates along the Y axis.
2341
+ * Returns the element that is being used as a placeholder
2342
+ * while the current element is being dragged.
2336
2343
  * @return {?}
2337
2344
  */
2338
- _isPointerNearDropContainer(pointerX, pointerY) {
2339
- const { top, right, bottom, left, width, height } = this._clientRect;
2340
- /** @type {?} */
2341
- const xThreshold = width * DROP_PROXIMITY_THRESHOLD;
2342
- /** @type {?} */
2343
- const yThreshold = height * DROP_PROXIMITY_THRESHOLD;
2344
- return pointerY > top - yThreshold && pointerY < bottom + yThreshold &&
2345
- pointerX > left - xThreshold && pointerX < right + xThreshold;
2345
+ getPlaceholderElement() {
2346
+ return this._dragRef.getPlaceholderElement();
2346
2347
  }
2347
2348
  /**
2348
- * Gets the offset in pixels by which the item that is being dragged should be moved.
2349
- * @private
2350
- * @param {?} currentPosition Current position of the item.
2351
- * @param {?} newPosition Position of the item where the current item should be moved.
2352
- * @param {?} delta Direction in which the user is moving.
2349
+ * Returns the root draggable element.
2353
2350
  * @return {?}
2354
2351
  */
2355
- _getItemOffsetPx(currentPosition, newPosition, delta) {
2356
- /** @type {?} */
2357
- const isHorizontal = this._orientation === 'horizontal';
2358
- /** @type {?} */
2359
- let itemOffset = isHorizontal ? newPosition.left - currentPosition.left :
2360
- newPosition.top - currentPosition.top;
2361
- // Account for differences in the item width/height.
2362
- if (delta === -1) {
2363
- itemOffset += isHorizontal ? newPosition.width - currentPosition.width :
2364
- newPosition.height - currentPosition.height;
2365
- }
2366
- return itemOffset;
2352
+ getRootElement() {
2353
+ return this._dragRef.getRootElement();
2367
2354
  }
2368
2355
  /**
2369
- * Gets the index of an item in the drop container, based on the position of the user's pointer.
2370
- * @private
2371
- * @param {?} item Item that is being sorted.
2372
- * @param {?} pointerX Position of the user's pointer along the X axis.
2373
- * @param {?} pointerY Position of the user's pointer along the Y axis.
2374
- * @param {?=} delta Direction in which the user is moving their pointer.
2356
+ * Resets a standalone drag item to its initial position.
2375
2357
  * @return {?}
2376
2358
  */
2377
- _getItemIndexFromPointerPosition(item, pointerX, pointerY, delta) {
2378
- /** @type {?} */
2379
- const isHorizontal = this._orientation === 'horizontal';
2380
- return findIndex(this._itemPositions, ({ drag, clientRect }, _, array) => {
2381
- if (drag === item) {
2382
- // If there's only one item left in the container, it must be
2383
- // the dragged item itself so we use it as a reference.
2384
- return array.length < 2;
2385
- }
2386
- if (delta) {
2359
+ reset() {
2360
+ this._dragRef.reset();
2361
+ }
2362
+ /**
2363
+ * @return {?}
2364
+ */
2365
+ ngAfterViewInit() {
2366
+ // We need to wait for the zone to stabilize, in order for the reference
2367
+ // element to be in the proper place in the DOM. This is mostly relevant
2368
+ // for draggable elements inside portals since they get stamped out in
2369
+ // their original DOM position and then they get transferred to the portal.
2370
+ this._ngZone.onStable.asObservable()
2371
+ .pipe(take(1), takeUntil(this._destroyed))
2372
+ .subscribe(() => {
2373
+ this._updateRootElement();
2374
+ // Listen for any newly-added handles.
2375
+ this._handles.changes.pipe(startWith(this._handles),
2376
+ // Sync the new handles with the DragRef.
2377
+ tap((handles) => {
2387
2378
  /** @type {?} */
2388
- const direction = isHorizontal ? delta.x : delta.y;
2389
- // If the user is still hovering over the same item as last time, and they didn't change
2390
- // the direction in which they're dragging, we don't consider it a direction swap.
2391
- if (drag === this._previousSwap.drag && direction === this._previousSwap.delta) {
2392
- return false;
2393
- }
2394
- }
2395
- return isHorizontal ?
2396
- // Round these down since most browsers report client rects with
2397
- // sub-pixel precision, whereas the pointer coordinates are rounded to pixels.
2398
- pointerX >= Math.floor(clientRect.left) && pointerX <= Math.floor(clientRect.right) :
2399
- pointerY >= Math.floor(clientRect.top) && pointerY <= Math.floor(clientRect.bottom);
2379
+ const childHandleElements = handles
2380
+ .filter(handle => handle._parentDrag === this)
2381
+ .map(handle => handle.element);
2382
+ this._dragRef.withHandles(childHandleElements);
2383
+ }),
2384
+ // Listen if the state of any of the handles changes.
2385
+ switchMap((handles) => {
2386
+ return merge(...handles.map(item => item._stateChanges));
2387
+ }), takeUntil(this._destroyed)).subscribe(handleInstance => {
2388
+ // Enabled/disable the handle that changed in the DragRef.
2389
+ /** @type {?} */
2390
+ const dragRef = this._dragRef;
2391
+ /** @type {?} */
2392
+ const handle = handleInstance.element.nativeElement;
2393
+ handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle);
2394
+ });
2400
2395
  });
2401
2396
  }
2402
2397
  /**
2403
- * Checks whether the user's pointer is positioned over the container.
2404
- * @param {?} x Pointer position along the X axis.
2405
- * @param {?} y Pointer position along the Y axis.
2398
+ * @param {?} changes
2406
2399
  * @return {?}
2407
2400
  */
2408
- _isOverContainer(x, y) {
2409
- return isInsideClientRect(this._clientRect, x, y);
2401
+ ngOnChanges(changes) {
2402
+ /** @type {?} */
2403
+ const rootSelectorChange = changes.rootElementSelector;
2404
+ // We don't have to react to the first change since it's being
2405
+ // handled in `ngAfterViewInit` where it needs to be deferred.
2406
+ if (rootSelectorChange && !rootSelectorChange.firstChange) {
2407
+ this._updateRootElement();
2408
+ }
2410
2409
  }
2411
2410
  /**
2412
- * Figures out whether an item should be moved into a sibling
2413
- * drop container, based on its current position.
2414
- * @param {?} item Drag item that is being moved.
2415
- * @param {?} x Position of the item along the X axis.
2416
- * @param {?} y Position of the item along the Y axis.
2417
2411
  * @return {?}
2418
2412
  */
2419
- _getSiblingContainerFromPosition(item, x, y) {
2420
- return this._siblings.find(sibling => sibling._canReceive(item, x, y));
2413
+ ngOnDestroy() {
2414
+ this._destroyed.next();
2415
+ this._destroyed.complete();
2416
+ this._dragRef.dispose();
2421
2417
  }
2422
2418
  /**
2423
- * Checks whether the drop list can receive the passed-in item.
2424
- * @param {?} item Item that is being dragged into the list.
2425
- * @param {?} x Position of the item along the X axis.
2426
- * @param {?} y Position of the item along the Y axis.
2419
+ * Syncs the root element with the `DragRef`.
2420
+ * @private
2427
2421
  * @return {?}
2428
2422
  */
2429
- _canReceive(item, x, y) {
2430
- if (!this.enterPredicate(item, this) || !isInsideClientRect(this._clientRect, x, y)) {
2431
- return false;
2432
- }
2433
- /** @type {?} */
2434
- const elementFromPoint = this._document.elementFromPoint(x, y);
2435
- // If there's no element at the pointer position, then
2436
- // the client rect is probably scrolled out of the view.
2437
- if (!elementFromPoint) {
2438
- return false;
2439
- }
2423
+ _updateRootElement() {
2440
2424
  /** @type {?} */
2441
2425
  const element = this.element.nativeElement;
2442
- // The `ClientRect`, that we're using to find the container over which the user is
2443
- // hovering, doesn't give us any information on whether the element has been scrolled
2444
- // out of the view or whether it's overlapping with other containers. This means that
2445
- // we could end up transferring the item into a container that's invisible or is positioned
2446
- // below another one. We use the result from `elementFromPoint` to get the top-most element
2447
- // at the pointer position and to find whether it's one of the intersecting drop containers.
2448
- return elementFromPoint === element || element.contains(elementFromPoint);
2426
+ /** @type {?} */
2427
+ const rootElement = this.rootElementSelector ?
2428
+ getClosestMatchingAncestor(element, this.rootElementSelector) : element;
2429
+ if (rootElement && rootElement.nodeType !== this._document.ELEMENT_NODE) {
2430
+ throw Error(`cdkDrag must be attached to an element node. ` +
2431
+ `Currently attached to "${rootElement.nodeName}".`);
2432
+ }
2433
+ this._dragRef.withRootElement(rootElement || element);
2449
2434
  }
2450
2435
  /**
2451
- * Called by one of the connected drop lists when a dragging sequence has started.
2452
- * @param {?} sibling Sibling in which dragging has started.
2436
+ * Gets the boundary element, based on the `boundaryElementSelector`.
2437
+ * @private
2453
2438
  * @return {?}
2454
2439
  */
2455
- _startReceiving(sibling) {
2440
+ _getBoundaryElement() {
2456
2441
  /** @type {?} */
2457
- const activeSiblings = this._activeSiblings;
2458
- if (!activeSiblings.has(sibling)) {
2459
- activeSiblings.add(sibling);
2460
- this._cacheOwnPosition();
2461
- }
2442
+ const selector = this.boundaryElementSelector;
2443
+ return selector ? getClosestMatchingAncestor(this.element.nativeElement, selector) : null;
2462
2444
  }
2463
2445
  /**
2464
- * Called by a connected drop list when dragging has stopped.
2465
- * @param {?} sibling Sibling whose dragging has stopped.
2446
+ * Syncs the inputs of the CdkDrag with the options of the underlying DragRef.
2447
+ * @private
2448
+ * @param {?} ref
2466
2449
  * @return {?}
2467
2450
  */
2468
- _stopReceiving(sibling) {
2469
- this._activeSiblings.delete(sibling);
2451
+ _syncInputs(ref) {
2452
+ ref.beforeStarted.subscribe(() => {
2453
+ if (!ref.isDragging()) {
2454
+ /** @type {?} */
2455
+ const dir = this._dir;
2456
+ /** @type {?} */
2457
+ const placeholder = this._placeholderTemplate ? {
2458
+ template: this._placeholderTemplate.templateRef,
2459
+ context: this._placeholderTemplate.data,
2460
+ viewContainer: this._viewContainerRef
2461
+ } : null;
2462
+ /** @type {?} */
2463
+ const preview = this._previewTemplate ? {
2464
+ template: this._previewTemplate.templateRef,
2465
+ context: this._previewTemplate.data,
2466
+ viewContainer: this._viewContainerRef
2467
+ } : null;
2468
+ ref.disabled = this.disabled;
2469
+ ref.lockAxis = this.lockAxis;
2470
+ ref
2471
+ .withBoundaryElement(this._getBoundaryElement())
2472
+ .withPlaceholderTemplate(placeholder)
2473
+ .withPreviewTemplate(preview);
2474
+ if (dir) {
2475
+ ref.withDirection(dir.value);
2476
+ }
2477
+ }
2478
+ });
2479
+ }
2480
+ /**
2481
+ * Proxies the events from a DragRef to events that
2482
+ * match the interfaces of the CdkDrag outputs.
2483
+ * @private
2484
+ * @param {?} ref
2485
+ * @return {?}
2486
+ */
2487
+ _proxyEvents(ref) {
2488
+ ref.started.subscribe(() => {
2489
+ this.started.emit({ source: this });
2490
+ });
2491
+ ref.released.subscribe(() => {
2492
+ this.released.emit({ source: this });
2493
+ });
2494
+ ref.ended.subscribe(() => {
2495
+ this.ended.emit({ source: this });
2496
+ });
2497
+ ref.entered.subscribe(event => {
2498
+ this.entered.emit({
2499
+ container: event.container.data,
2500
+ item: this
2501
+ });
2502
+ });
2503
+ ref.exited.subscribe(event => {
2504
+ this.exited.emit({
2505
+ container: event.container.data,
2506
+ item: this
2507
+ });
2508
+ });
2509
+ ref.dropped.subscribe(event => {
2510
+ this.dropped.emit({
2511
+ previousIndex: event.previousIndex,
2512
+ currentIndex: event.currentIndex,
2513
+ previousContainer: event.previousContainer.data,
2514
+ container: event.container.data,
2515
+ isPointerOverContainer: event.isPointerOverContainer,
2516
+ item: this
2517
+ });
2518
+ });
2470
2519
  }
2471
2520
  }
2521
+ CdkDrag.decorators = [
2522
+ { type: Directive, args: [{
2523
+ selector: '[cdkDrag]',
2524
+ exportAs: 'cdkDrag',
2525
+ host: {
2526
+ 'class': 'cdk-drag',
2527
+ '[class.cdk-drag-dragging]': '_dragRef.isDragging()',
2528
+ },
2529
+ providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }]
2530
+ },] },
2531
+ ];
2532
+ /** @nocollapse */
2533
+ CdkDrag.ctorParameters = () => [
2534
+ { type: ElementRef },
2535
+ { type: undefined, decorators: [{ type: Inject, args: [CDK_DROP_LIST,] }, { type: Optional }, { type: SkipSelf }] },
2536
+ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2537
+ { type: NgZone },
2538
+ { type: ViewContainerRef },
2539
+ { type: ViewportRuler },
2540
+ { type: DragDropRegistry },
2541
+ { type: undefined, decorators: [{ type: Inject, args: [CDK_DRAG_CONFIG,] }] },
2542
+ { type: Directionality, decorators: [{ type: Optional }] },
2543
+ { type: DragDrop }
2544
+ ];
2545
+ CdkDrag.propDecorators = {
2546
+ _handles: [{ type: ContentChildren, args: [CdkDragHandle, { descendants: true },] }],
2547
+ _previewTemplate: [{ type: ContentChild, args: [CdkDragPreview,] }],
2548
+ _placeholderTemplate: [{ type: ContentChild, args: [CdkDragPlaceholder,] }],
2549
+ data: [{ type: Input, args: ['cdkDragData',] }],
2550
+ lockAxis: [{ type: Input, args: ['cdkDragLockAxis',] }],
2551
+ rootElementSelector: [{ type: Input, args: ['cdkDragRootElement',] }],
2552
+ boundaryElementSelector: [{ type: Input, args: ['cdkDragBoundary',] }],
2553
+ disabled: [{ type: Input, args: ['cdkDragDisabled',] }],
2554
+ started: [{ type: Output, args: ['cdkDragStarted',] }],
2555
+ released: [{ type: Output, args: ['cdkDragReleased',] }],
2556
+ ended: [{ type: Output, args: ['cdkDragEnded',] }],
2557
+ entered: [{ type: Output, args: ['cdkDragEntered',] }],
2558
+ exited: [{ type: Output, args: ['cdkDragExited',] }],
2559
+ dropped: [{ type: Output, args: ['cdkDragDropped',] }],
2560
+ moved: [{ type: Output, args: ['cdkDragMoved',] }]
2561
+ };
2472
2562
  /**
2473
- * Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
2474
- * @param {?} clientRect `ClientRect` that should be updated.
2475
- * @param {?} top Amount to add to the `top` position.
2476
- * @param {?} left Amount to add to the `left` position.
2477
- * @return {?}
2478
- */
2479
- function adjustClientRect(clientRect, top, left) {
2480
- clientRect.top += top;
2481
- clientRect.bottom = clientRect.top + clientRect.height;
2482
- clientRect.left += left;
2483
- clientRect.right = clientRect.left + clientRect.width;
2484
- }
2485
- /**
2486
- * Finds the index of an item that matches a predicate function. Used as an equivalent
2487
- * of `Array.prototype.find` which isn't part of the standard Google typings.
2488
- * @template T
2489
- * @param {?} array Array in which to look for matches.
2490
- * @param {?} predicate Function used to determine whether an item is a match.
2563
+ * Gets the closest ancestor of an element that matches a selector.
2564
+ * @param {?} element
2565
+ * @param {?} selector
2491
2566
  * @return {?}
2492
2567
  */
2493
- function findIndex(array, predicate) {
2494
- for (let i = 0; i < array.length; i++) {
2495
- if (predicate(array[i], i, array)) {
2496
- return i;
2568
+ function getClosestMatchingAncestor(element, selector) {
2569
+ /** @type {?} */
2570
+ let currentElement = (/** @type {?} */ (element.parentElement));
2571
+ while (currentElement) {
2572
+ // IE doesn't support `matches` so we have to fall back to `msMatchesSelector`.
2573
+ if (currentElement.matches ? currentElement.matches(selector) :
2574
+ ((/** @type {?} */ (currentElement))).msMatchesSelector(selector)) {
2575
+ return currentElement;
2497
2576
  }
2577
+ currentElement = currentElement.parentElement;
2498
2578
  }
2499
- return -1;
2579
+ return null;
2500
2580
  }
2581
+
2501
2582
  /**
2502
- * Checks whether some coordinates are within a `ClientRect`.
2503
- * @param {?} clientRect ClientRect that is being checked.
2504
- * @param {?} x Coordinates along the X axis.
2505
- * @param {?} y Coordinates along the Y axis.
2506
- * @return {?}
2583
+ * @fileoverview added by tsickle
2584
+ * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2507
2585
  */
2508
- function isInsideClientRect(clientRect, x, y) {
2509
- const { top, bottom, left, right } = clientRect;
2510
- return y >= top && y <= bottom && x >= left && x <= right;
2586
+ /**
2587
+ * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`
2588
+ * elements that are placed inside a `cdkDropListGroup` will be connected to each other
2589
+ * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input
2590
+ * from `cdkDropList`.
2591
+ * @template T
2592
+ */
2593
+ class CdkDropListGroup {
2594
+ constructor() {
2595
+ /**
2596
+ * Drop lists registered inside the group.
2597
+ */
2598
+ this._items = new Set();
2599
+ this._disabled = false;
2600
+ }
2601
+ /**
2602
+ * Whether starting a dragging sequence from inside this group is disabled.
2603
+ * @return {?}
2604
+ */
2605
+ get disabled() { return this._disabled; }
2606
+ /**
2607
+ * @param {?} value
2608
+ * @return {?}
2609
+ */
2610
+ set disabled(value) {
2611
+ this._disabled = coerceBooleanProperty(value);
2612
+ }
2613
+ /**
2614
+ * @return {?}
2615
+ */
2616
+ ngOnDestroy() {
2617
+ this._items.clear();
2618
+ }
2511
2619
  }
2620
+ CdkDropListGroup.decorators = [
2621
+ { type: Directive, args: [{
2622
+ selector: '[cdkDropListGroup]',
2623
+ exportAs: 'cdkDropListGroup',
2624
+ },] },
2625
+ ];
2626
+ CdkDropListGroup.propDecorators = {
2627
+ disabled: [{ type: Input, args: ['cdkDropListGroupDisabled',] }]
2628
+ };
2512
2629
 
2513
2630
  /**
2514
2631
  * @fileoverview added by tsickle
@@ -2532,16 +2649,26 @@ class CdkDropList {
2532
2649
  * @param {?} element
2533
2650
  * @param {?} dragDropRegistry
2534
2651
  * @param {?} _changeDetectorRef
2535
- * @param {?=} dir
2652
+ * @param {?=} _dir
2536
2653
  * @param {?=} _group
2537
2654
  * @param {?=} _document
2655
+ * @param {?=} dragDrop
2656
+ */
2657
+ constructor(element, dragDropRegistry, _changeDetectorRef, _dir, _group, _document,
2658
+ /**
2659
+ * @deprecated `dragDropRegistry` and `_document` parameters to be removed.
2660
+ * Also `dragDrop` parameter to be made required.
2661
+ * @breaking-change 8.0.0.
2538
2662
  */
2539
- constructor(element, dragDropRegistry, _changeDetectorRef, dir, _group,
2540
- // @breaking-change 8.0.0 `_document` parameter to be made required.
2541
- _document) {
2663
+ dragDrop) {
2542
2664
  this.element = element;
2543
2665
  this._changeDetectorRef = _changeDetectorRef;
2666
+ this._dir = _dir;
2544
2667
  this._group = _group;
2668
+ /**
2669
+ * Emits when the list has been destroyed.
2670
+ */
2671
+ this._destroyed = new Subject();
2545
2672
  /**
2546
2673
  * Other draggable containers that this container is connected to and into which the
2547
2674
  * container's items can be transferred. Can either be references to other drop containers,
@@ -2580,15 +2707,19 @@ class CdkDropList {
2580
2707
  * Emits as the user is swapping items while actively dragging.
2581
2708
  */
2582
2709
  this.sorted = new EventEmitter();
2583
- // @breaking-change 8.0.0 Remove || once `_document` parameter is required.
2584
- /** @type {?} */
2585
- const ref = this._dropListRef = new DropListRef(element, dragDropRegistry, _document || document, dir);
2586
- ref.data = this;
2587
- ref.enterPredicate = (drag, drop) => {
2710
+ // @breaking-change 8.0.0 Remove null check once `dragDrop` parameter is made required.
2711
+ if (dragDrop) {
2712
+ this._dropListRef = dragDrop.createDropList(element);
2713
+ }
2714
+ else {
2715
+ this._dropListRef = new DropListRef(element, dragDropRegistry, _document || document);
2716
+ }
2717
+ this._dropListRef.data = this;
2718
+ this._dropListRef.enterPredicate = (drag, drop) => {
2588
2719
  return this.enterPredicate(drag.data, drop.data);
2589
2720
  };
2590
- this._syncInputs(ref);
2591
- this._handleEvents(ref);
2721
+ this._syncInputs(this._dropListRef);
2722
+ this._handleEvents(this._dropListRef);
2592
2723
  CdkDropList._dropLists.push(this);
2593
2724
  if (_group) {
2594
2725
  _group._items.add(this);
@@ -2608,19 +2739,31 @@ class CdkDropList {
2608
2739
  set disabled(value) {
2609
2740
  this._disabled = coerceBooleanProperty(value);
2610
2741
  }
2742
+ /**
2743
+ * @return {?}
2744
+ */
2745
+ ngAfterContentInit() {
2746
+ this._draggables.changes
2747
+ .pipe(startWith(this._draggables), takeUntil(this._destroyed))
2748
+ .subscribe((items) => {
2749
+ this._dropListRef.withItems(items.map(drag => drag._dragRef));
2750
+ });
2751
+ }
2611
2752
  /**
2612
2753
  * @return {?}
2613
2754
  */
2614
2755
  ngOnDestroy() {
2615
2756
  /** @type {?} */
2616
2757
  const index = CdkDropList._dropLists.indexOf(this);
2617
- this._dropListRef.dispose();
2618
2758
  if (index > -1) {
2619
2759
  CdkDropList._dropLists.splice(index, 1);
2620
2760
  }
2621
2761
  if (this._group) {
2622
2762
  this._group._items.delete(this);
2623
2763
  }
2764
+ this._dropListRef.dispose();
2765
+ this._destroyed.next();
2766
+ this._destroyed.complete();
2624
2767
  }
2625
2768
  /**
2626
2769
  * Starts dragging an item.
@@ -2707,6 +2850,11 @@ class CdkDropList {
2707
2850
  * @return {?}
2708
2851
  */
2709
2852
  _syncInputs(ref) {
2853
+ if (this._dir) {
2854
+ this._dir.change
2855
+ .pipe(startWith(this._dir.value), takeUntil(this._destroyed))
2856
+ .subscribe(value => ref.withDirection(value));
2857
+ }
2710
2858
  ref.beforeStarted.subscribe(() => {
2711
2859
  /** @type {?} */
2712
2860
  const siblings = coerceArray(this.connectedTo).map(drop => {
@@ -2723,8 +2871,7 @@ class CdkDropList {
2723
2871
  ref.lockAxis = this.lockAxis;
2724
2872
  ref
2725
2873
  .connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
2726
- .withOrientation(this.orientation)
2727
- .withItems(this._draggables.map(drag => drag._dragRef));
2874
+ .withOrientation(this.orientation);
2728
2875
  });
2729
2876
  }
2730
2877
  /**
@@ -2800,10 +2947,15 @@ CdkDropList.ctorParameters = () => [
2800
2947
  { type: ChangeDetectorRef },
2801
2948
  { type: Directionality, decorators: [{ type: Optional }] },
2802
2949
  { type: CdkDropListGroup, decorators: [{ type: Optional }, { type: SkipSelf }] },
2803
- { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] }
2950
+ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] },
2951
+ { type: DragDrop }
2804
2952
  ];
2805
2953
  CdkDropList.propDecorators = {
2806
- _draggables: [{ type: ContentChildren, args: [forwardRef(() => CdkDrag),] }],
2954
+ _draggables: [{ type: ContentChildren, args: [forwardRef(() => CdkDrag), {
2955
+ // Explicitly set to false since some of the logic below makes assumptions about it.
2956
+ // The `.withItems` call below should be updated if we ever need to switch this to `true`.
2957
+ descendants: false
2958
+ },] }],
2807
2959
  connectedTo: [{ type: Input, args: ['cdkDropListConnectedTo',] }],
2808
2960
  data: [{ type: Input, args: ['cdkDropListData',] }],
2809
2961
  orientation: [{ type: Input, args: ['cdkDropListOrientation',] }],
@@ -2841,6 +2993,9 @@ DragDropModule.decorators = [
2841
2993
  CdkDragPreview,
2842
2994
  CdkDragPlaceholder,
2843
2995
  ],
2996
+ providers: [
2997
+ DragDrop,
2998
+ ]
2844
2999
  },] },
2845
3000
  ];
2846
3001
 
@@ -2854,5 +3009,5 @@ DragDropModule.decorators = [
2854
3009
  * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
2855
3010
  */
2856
3011
 
2857
- export { CDK_DROP_LIST, CDK_DROP_LIST_CONTAINER, moveItemInArray, transferArrayItem, copyArrayItem, DragDropModule, DragDropRegistry, CdkDropList, CdkDropListGroup, CDK_DRAG_CONFIG_FACTORY, CDK_DRAG_CONFIG, CdkDrag, CdkDragHandle, CdkDragPreview, CdkDragPlaceholder, CDK_DRAG_PARENT as ɵa };
3012
+ export { DragDrop, DragRef, DropListRef, CdkDropList, CDK_DROP_LIST, CDK_DROP_LIST_CONTAINER, moveItemInArray, transferArrayItem, copyArrayItem, DragDropModule, DragDropRegistry, CdkDropListGroup, CDK_DRAG_CONFIG_FACTORY, CDK_DRAG_CONFIG, CdkDrag, CdkDragHandle, CdkDragPreview, CdkDragPlaceholder, CDK_DRAG_PARENT as ɵb };
2858
3013
  //# sourceMappingURL=drag-drop.js.map