Skip to content

Commit 593a06c

Browse files
crisbetommalerba
authored andcommitted
fix(drag-drop): unable to drop into connected list inside shad… (#17424)
In #16899 we added some logic to account for connected drop lists inside the shadow DOM, however the logic was put in the constructor which is too early since the element might not be moved into the shadow DOM yet, if it is inserted through something like `ngFor` or `ngIf`. These changes fix the issue by resolving the shadow root lazily the first time it is requested, which should be more than enough time for the element to have been moved into the shadow DOM. Fixes #17422.
1 parent 939c18d commit 593a06c

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4233,6 +4233,40 @@ describe('CdkDrag', () => {
42334233
});
42344234
}));
42354235

4236+
it('should be able to drop into a new container inside the Shadow DOM and ngIf',
4237+
fakeAsync(() => {
4238+
// This test is only relevant for Shadow DOM-supporting browsers.
4239+
if (!_supportsShadowDom()) {
4240+
return;
4241+
}
4242+
4243+
const fixture = createComponent(ConnectedDropZonesInsideShadowRootWithNgIf);
4244+
fixture.detectChanges();
4245+
4246+
const groups = fixture.componentInstance.groupedDragItems;
4247+
const item = groups[0][1];
4248+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
4249+
4250+
dragElementViaMouse(fixture, item.element.nativeElement,
4251+
targetRect.left + 1, targetRect.top + 1);
4252+
flush();
4253+
fixture.detectChanges();
4254+
4255+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
4256+
4257+
const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
4258+
4259+
expect(event).toEqual({
4260+
previousIndex: 1,
4261+
currentIndex: 3,
4262+
item,
4263+
container: fixture.componentInstance.dropInstances.toArray()[1],
4264+
previousContainer: fixture.componentInstance.dropInstances.first,
4265+
isPointerOverContainer: true,
4266+
distance: {x: jasmine.any(Number), y: jasmine.any(Number)}
4267+
});
4268+
}));
4269+
42364270
});
42374271

42384272
describe('with nested drags', () => {
@@ -4744,6 +4778,13 @@ class ConnectedDropZones implements AfterViewInit {
47444778
class ConnectedDropZonesInsideShadowRoot extends ConnectedDropZones {
47454779
}
47464780

4781+
@Component({
4782+
encapsulation: ViewEncapsulation.ShadowDom,
4783+
styles: CONNECTED_DROP_ZONES_STYLES,
4784+
template: `<div *ngIf="true">${CONNECTED_DROP_ZONES_TEMPLATE}</div>`
4785+
})
4786+
class ConnectedDropZonesInsideShadowRootWithNgIf extends ConnectedDropZones {
4787+
}
47474788

47484789
@Component({
47494790
encapsulation: ViewEncapsulation.None,

src/cdk/drag-drop/drop-list-ref.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,19 @@ export class DropListRef<T = any> {
190190
private _stopScrollTimers = new Subject<void>();
191191

192192
/** Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly. */
193-
private _shadowRoot: DocumentOrShadowRoot;
193+
private _cachedShadowRoot: DocumentOrShadowRoot | null = null;
194+
195+
/** Reference to the document. */
196+
private _document: Document;
194197

195198
constructor(
196199
element: ElementRef<HTMLElement> | HTMLElement,
197200
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
198201
_document: any,
199202
private _ngZone: NgZone,
200203
private _viewportRuler: ViewportRuler) {
201-
const nativeNode = this.element = coerceElement(element);
202-
this._shadowRoot = getShadowRoot(nativeNode) || _document;
204+
this.element = coerceElement(element);
205+
this._document = _document;
203206
_dragDropRegistry.registerDropContainer(this);
204207
}
205208

@@ -785,7 +788,7 @@ export class DropListRef<T = any> {
785788
return false;
786789
}
787790

788-
const elementFromPoint = this._shadowRoot.elementFromPoint(x, y) as HTMLElement | null;
791+
const elementFromPoint = this._getShadowRoot().elementFromPoint(x, y) as HTMLElement | null;
789792

790793
// If there's no element at the pointer position, then
791794
// the client rect is probably scrolled out of the view.
@@ -843,6 +846,20 @@ export class DropListRef<T = any> {
843846
}
844847
});
845848
}
849+
850+
/**
851+
* Lazily resolves and returns the shadow root of the element. We do this in a function, rather
852+
* than saving it in property directly on init, because we want to resolve it as late as possible
853+
* in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
854+
* constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
855+
*/
856+
private _getShadowRoot(): DocumentOrShadowRoot {
857+
if (!this._cachedShadowRoot) {
858+
this._cachedShadowRoot = getShadowRoot(coerceElement(this.element)) || this._document;
859+
}
860+
861+
return this._cachedShadowRoot;
862+
}
846863
}
847864

848865

0 commit comments

Comments
 (0)