@@ -141,6 +141,9 @@ export class DropListRef<T = any> {
141141 /** Arbitrary data that can be attached to the drop list. */
142142 data : T ;
143143
144+ /** Element that is the direct parent of the drag items. */
145+ private _container : HTMLElement ;
146+
144147 /** Whether an item in the list is being dragged. */
145148 private _isDragging = false ;
146149
@@ -184,7 +187,7 @@ export class DropListRef<T = any> {
184187 private _document : Document ;
185188
186189 /** Elements that can be scrolled while the user is dragging. */
187- private _scrollableElements : HTMLElement [ ] ;
190+ private _scrollableElements : HTMLElement [ ] = [ ] ;
188191
189192 /** Initial value for the element's `scroll-snap-type` style. */
190193 private _initialScrollSnap : string ;
@@ -199,9 +202,9 @@ export class DropListRef<T = any> {
199202 private _ngZone : NgZone ,
200203 private _viewportRuler : ViewportRuler ,
201204 ) {
202- this . element = coerceElement ( element ) ;
205+ const coercedElement = ( this . element = coerceElement ( element ) ) ;
203206 this . _document = _document ;
204- this . withScrollableParents ( [ this . element ] ) . withOrientation ( 'vertical' ) ;
207+ this . withOrientation ( 'vertical' ) . withElementContainer ( coercedElement ) ;
205208 _dragDropRegistry . registerDropContainer ( this ) ;
206209 this . _parentPositions = new ParentPositionTracker ( _document ) ;
207210 }
@@ -358,20 +361,14 @@ export class DropListRef<T = any> {
358361 */
359362 withOrientation ( orientation : DropListOrientation ) : this {
360363 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 ) ;
366365 } else {
367- const strategy = new SingleAxisSortStrategy (
368- coerceElement ( this . element ) ,
369- this . _dragDropRegistry ,
370- ) ;
366+ const strategy = new SingleAxisSortStrategy ( this . _dragDropRegistry ) ;
371367 strategy . direction = this . _direction ;
372368 strategy . orientation = orientation ;
373369 this . _sortStrategy = strategy ;
374370 }
371+ this . _sortStrategy . withElementContainer ( this . _container ) ;
375372 this . _sortStrategy . withSortPredicate ( ( index , item ) => this . sortPredicate ( index , item , this ) ) ;
376373 return this ;
377374 }
@@ -381,7 +378,7 @@ export class DropListRef<T = any> {
381378 * @param elements Elements that can be scrolled.
382379 */
383380 withScrollableParents ( elements : HTMLElement [ ] ) : this {
384- const element = coerceElement ( this . element ) ;
381+ const element = this . _container ;
385382
386383 // We always allow the current element to be scrollable
387384 // so we need to ensure that it's in the array.
@@ -390,6 +387,51 @@ export class DropListRef<T = any> {
390387 return this ;
391388 }
392389
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+
393435 /** Gets the scrollable parents that are registered with this drop container. */
394436 getScrollableParents ( ) : readonly HTMLElement [ ] {
395437 return this . _scrollableElements ;
@@ -526,10 +568,25 @@ export class DropListRef<T = any> {
526568
527569 /** Starts the dragging sequence within the list. */
528570 private _draggingStarted ( ) {
529- const styles = coerceElement ( this . element ) . style as DragCSSStyleDeclaration ;
571+ const styles = this . _container . style as DragCSSStyleDeclaration ;
530572 this . beforeStarted . next ( ) ;
531573 this . _isDragging = true ;
532574
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+
533590 // We need to disable scroll snapping while the user is dragging, because it breaks automatic
534591 // scrolling. The browser seems to round the value based on the snapping points which means
535592 // that we can't increment/decrement the scroll position.
@@ -543,19 +600,17 @@ export class DropListRef<T = any> {
543600
544601 /** Caches the positions of the configured scrollable parents. */
545602 private _cacheParentPositions ( ) {
546- const element = coerceElement ( this . element ) ;
547603 this . _parentPositions . cache ( this . _scrollableElements ) ;
548604
549605 // The list element is always in the `scrollableElements`
550606 // 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 ! ;
552608 }
553609
554610 /** Resets the container to its initial state. */
555611 private _reset ( ) {
556612 this . _isDragging = false ;
557-
558- const styles = coerceElement ( this . element ) . style as DragCSSStyleDeclaration ;
613+ const styles = this . _container . style as DragCSSStyleDeclaration ;
559614 styles . scrollSnapType = styles . msScrollSnapType = this . _initialScrollSnap ;
560615
561616 this . _siblings . forEach ( sibling => sibling . _stopReceiving ( this ) ) ;
@@ -632,15 +687,13 @@ export class DropListRef<T = any> {
632687 return false ;
633688 }
634689
635- const nativeElement = coerceElement ( this . element ) ;
636-
637690 // The `DOMRect`, that we're using to find the container over which the user is
638691 // hovering, doesn't give us any information on whether the element has been scrolled
639692 // out of the view or whether it's overlapping with other containers. This means that
640693 // we could end up transferring the item into a container that's invisible or is positioned
641694 // below another one. We use the result from `elementFromPoint` to get the top-most element
642695 // 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 ) ;
644697 }
645698
646699 /**
@@ -709,7 +762,7 @@ export class DropListRef<T = any> {
709762 */
710763 private _getShadowRoot ( ) : RootNode {
711764 if ( ! this . _cachedShadowRoot ) {
712- const shadowRoot = _getShadowRoot ( coerceElement ( this . element ) ) ;
765+ const shadowRoot = _getShadowRoot ( this . _container ) ;
713766 this . _cachedShadowRoot = ( shadowRoot || this . _document ) as RootNode ;
714767 }
715768
0 commit comments