@@ -141,6 +141,9 @@ export class DropListRef<T = any> {
141
141
/** Arbitrary data that can be attached to the drop list. */
142
142
data : T ;
143
143
144
+ /** Element that is the direct parent of the drag items. */
145
+ private _container : HTMLElement ;
146
+
144
147
/** Whether an item in the list is being dragged. */
145
148
private _isDragging = false ;
146
149
@@ -184,7 +187,7 @@ export class DropListRef<T = any> {
184
187
private _document : Document ;
185
188
186
189
/** Elements that can be scrolled while the user is dragging. */
187
- private _scrollableElements : HTMLElement [ ] ;
190
+ private _scrollableElements : HTMLElement [ ] = [ ] ;
188
191
189
192
/** Initial value for the element's `scroll-snap-type` style. */
190
193
private _initialScrollSnap : string ;
@@ -199,9 +202,9 @@ export class DropListRef<T = any> {
199
202
private _ngZone : NgZone ,
200
203
private _viewportRuler : ViewportRuler ,
201
204
) {
202
- this . element = coerceElement ( element ) ;
205
+ const coercedElement = ( this . element = coerceElement ( element ) ) ;
203
206
this . _document = _document ;
204
- this . withScrollableParents ( [ this . element ] ) . withOrientation ( 'vertical' ) ;
207
+ this . withOrientation ( 'vertical' ) . withElementContainer ( coercedElement ) ;
205
208
_dragDropRegistry . registerDropContainer ( this ) ;
206
209
this . _parentPositions = new ParentPositionTracker ( _document ) ;
207
210
}
@@ -358,20 +361,14 @@ export class DropListRef<T = any> {
358
361
*/
359
362
withOrientation ( orientation : DropListOrientation ) : this {
360
363
if ( orientation === 'mixed' ) {
361
- this . _sortStrategy = new MixedSortStrategy (
362
- coerceElement ( this . element ) ,
363
- this . _document ,
364
- this . _dragDropRegistry ,
365
- ) ;
364
+ this . _sortStrategy = new MixedSortStrategy ( this . _document , this . _dragDropRegistry ) ;
366
365
} else {
367
- const strategy = new SingleAxisSortStrategy (
368
- coerceElement ( this . element ) ,
369
- this . _dragDropRegistry ,
370
- ) ;
366
+ const strategy = new SingleAxisSortStrategy ( this . _dragDropRegistry ) ;
371
367
strategy . direction = this . _direction ;
372
368
strategy . orientation = orientation ;
373
369
this . _sortStrategy = strategy ;
374
370
}
371
+ this . _sortStrategy . withElementContainer ( this . _container ) ;
375
372
this . _sortStrategy . withSortPredicate ( ( index , item ) => this . sortPredicate ( index , item , this ) ) ;
376
373
return this ;
377
374
}
@@ -381,7 +378,7 @@ export class DropListRef<T = any> {
381
378
* @param elements Elements that can be scrolled.
382
379
*/
383
380
withScrollableParents ( elements : HTMLElement [ ] ) : this {
384
- const element = coerceElement ( this . element ) ;
381
+ const element = this . _container ;
385
382
386
383
// We always allow the current element to be scrollable
387
384
// so we need to ensure that it's in the array.
@@ -390,6 +387,51 @@ export class DropListRef<T = any> {
390
387
return this ;
391
388
}
392
389
390
+ /**
391
+ * Configures the drop list so that a different element is used as the container for the
392
+ * dragged items. This is useful for the cases when one might not have control over the
393
+ * full DOM that sets up the dragging.
394
+ * Note that the alternate container needs to be a descendant of the drop list.
395
+ * @param container New element container to be assigned.
396
+ */
397
+ withElementContainer ( container : HTMLElement ) : this {
398
+ if ( container === this . _container ) {
399
+ return this ;
400
+ }
401
+
402
+ const element = coerceElement ( this . element ) ;
403
+
404
+ if (
405
+ ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
406
+ container !== element &&
407
+ ! element . contains ( container )
408
+ ) {
409
+ throw new Error (
410
+ 'Invalid DOM structure for drop list. Alternate container element must be a descendant of the drop list.' ,
411
+ ) ;
412
+ }
413
+
414
+ const oldContainerIndex = this . _scrollableElements . indexOf ( this . _container ) ;
415
+ const newContainerIndex = this . _scrollableElements . indexOf ( container ) ;
416
+
417
+ if ( oldContainerIndex > - 1 ) {
418
+ this . _scrollableElements . splice ( oldContainerIndex , 1 ) ;
419
+ }
420
+
421
+ if ( newContainerIndex > - 1 ) {
422
+ this . _scrollableElements . splice ( newContainerIndex , 1 ) ;
423
+ }
424
+
425
+ if ( this . _sortStrategy ) {
426
+ this . _sortStrategy . withElementContainer ( container ) ;
427
+ }
428
+
429
+ this . _cachedShadowRoot = null ;
430
+ this . _scrollableElements . unshift ( container ) ;
431
+ this . _container = container ;
432
+ return this ;
433
+ }
434
+
393
435
/** Gets the scrollable parents that are registered with this drop container. */
394
436
getScrollableParents ( ) : readonly HTMLElement [ ] {
395
437
return this . _scrollableElements ;
@@ -526,10 +568,25 @@ export class DropListRef<T = any> {
526
568
527
569
/** Starts the dragging sequence within the list. */
528
570
private _draggingStarted ( ) {
529
- const styles = coerceElement ( this . element ) . style as DragCSSStyleDeclaration ;
571
+ const styles = this . _container . style as DragCSSStyleDeclaration ;
530
572
this . beforeStarted . next ( ) ;
531
573
this . _isDragging = true ;
532
574
575
+ if (
576
+ ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
577
+ // Prevent the check from running on apps not using an alternate container. Ideally we
578
+ // would always run it, but introducing it at this stage would be a breaking change.
579
+ this . _container !== coerceElement ( this . element )
580
+ ) {
581
+ for ( const drag of this . _draggables ) {
582
+ if ( ! drag . isDragging ( ) && drag . getVisibleElement ( ) . parentNode !== this . _container ) {
583
+ throw new Error (
584
+ 'Invalid DOM structure for drop list. All items must be placed directly inside of the element container.' ,
585
+ ) ;
586
+ }
587
+ }
588
+ }
589
+
533
590
// We need to disable scroll snapping while the user is dragging, because it breaks automatic
534
591
// scrolling. The browser seems to round the value based on the snapping points which means
535
592
// that we can't increment/decrement the scroll position.
@@ -543,19 +600,17 @@ export class DropListRef<T = any> {
543
600
544
601
/** Caches the positions of the configured scrollable parents. */
545
602
private _cacheParentPositions ( ) {
546
- const element = coerceElement ( this . element ) ;
547
603
this . _parentPositions . cache ( this . _scrollableElements ) ;
548
604
549
605
// The list element is always in the `scrollableElements`
550
606
// so we can take advantage of the cached `DOMRect`.
551
- this . _domRect = this . _parentPositions . positions . get ( element ) ! . clientRect ! ;
607
+ this . _domRect = this . _parentPositions . positions . get ( this . _container ) ! . clientRect ! ;
552
608
}
553
609
554
610
/** Resets the container to its initial state. */
555
611
private _reset ( ) {
556
612
this . _isDragging = false ;
557
-
558
- const styles = coerceElement ( this . element ) . style as DragCSSStyleDeclaration ;
613
+ const styles = this . _container . style as DragCSSStyleDeclaration ;
559
614
styles . scrollSnapType = styles . msScrollSnapType = this . _initialScrollSnap ;
560
615
561
616
this . _siblings . forEach ( sibling => sibling . _stopReceiving ( this ) ) ;
@@ -632,15 +687,13 @@ export class DropListRef<T = any> {
632
687
return false ;
633
688
}
634
689
635
- const nativeElement = coerceElement ( this . element ) ;
636
-
637
690
// The `DOMRect`, that we're using to find the container over which the user is
638
691
// hovering, doesn't give us any information on whether the element has been scrolled
639
692
// out of the view or whether it's overlapping with other containers. This means that
640
693
// we could end up transferring the item into a container that's invisible or is positioned
641
694
// below another one. We use the result from `elementFromPoint` to get the top-most element
642
695
// at the pointer position and to find whether it's one of the intersecting drop containers.
643
- return elementFromPoint === nativeElement || nativeElement . contains ( elementFromPoint ) ;
696
+ return elementFromPoint === this . _container || this . _container . contains ( elementFromPoint ) ;
644
697
}
645
698
646
699
/**
@@ -709,7 +762,7 @@ export class DropListRef<T = any> {
709
762
*/
710
763
private _getShadowRoot ( ) : RootNode {
711
764
if ( ! this . _cachedShadowRoot ) {
712
- const shadowRoot = _getShadowRoot ( coerceElement ( this . element ) ) ;
765
+ const shadowRoot = _getShadowRoot ( this . _container ) ;
713
766
this . _cachedShadowRoot = ( shadowRoot || this . _document ) as RootNode ;
714
767
}
715
768
0 commit comments