Skip to content

fix(cdk/drag-drop): preview not inheriting styles inside shadow dom #21107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5464,6 +5464,24 @@ describe('CdkDrag', () => {
.toBe(1, 'Expected only one item to continue to be dragged.');
}));

it('should insert the preview inside the shadow root by default', fakeAsync(() => {
// This test is only relevant for Shadow DOM-supporting browsers.
if (!_supportsShadowDom()) {
return;
}

const fixture = createComponent(ConnectedDropZonesInsideShadowRoot);
fixture.detectChanges();
const item = fixture.componentInstance.groupedDragItems[0][1];

startDraggingViaMouse(fixture, item.element.nativeElement);
fixture.detectChanges();

// `querySelector` doesn't descend into the shadow DOM so we can assert that the preview
// isn't at its default location by searching for it at the `document` level.
expect(document.querySelector('.cdk-drag-preview')).toBeFalsy();
}));

});

describe('with nested drags', () => {
Expand Down
33 changes: 29 additions & 4 deletions src/cdk/drag-drop/drag-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {EmbeddedViewRef, ElementRef, NgZone, ViewContainerRef, TemplateRef} from '@angular/core';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {Direction} from '@angular/cdk/bidi';
import {normalizePassiveListenerOptions} from '@angular/cdk/platform';
import {normalizePassiveListenerOptions, _getShadowRoot} from '@angular/cdk/platform';
import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion';
import {Subscription, Subject, Observable} from 'rxjs';
import {DropListRefInternal as DropListRef} from './drop-list-ref';
Expand Down Expand Up @@ -227,6 +227,13 @@ export class DragRef<T = any> {
/** Layout direction of the item. */
private _direction: Direction = 'ltr';

/**
* Cached shadow root that the element is placed in. `null` means that the element isn't in
* the shadow DOM and `undefined` means that it hasn't been resolved yet. Should be read via
* `_getShadowRoot`, not directly.
*/
private _cachedShadowRoot: ShadowRoot | null | undefined;

/** Axis along which dragging is locked. */
lockAxis: 'x' | 'y';

Expand Down Expand Up @@ -743,6 +750,9 @@ export class DragRef<T = any> {
const placeholder = this._placeholder = this._createPlaceholderElement();
const anchor = this._anchor = this._anchor || this._document.createComment('');

// Needs to happen before the root element is moved.
const shadowRoot = this._getShadowRoot();

// Insert an anchor node so that we can restore the element's position in the DOM.
parent.insertBefore(anchor, element);

Expand All @@ -751,7 +761,7 @@ export class DragRef<T = any> {
// from the DOM completely, because iOS will stop firing all subsequent events in the chain.
toggleVisibility(element, false);
this._document.body.appendChild(parent.replaceChild(placeholder, element));
getPreviewInsertionPoint(this._document).appendChild(preview);
getPreviewInsertionPoint(this._document, shadowRoot).appendChild(preview);
this.started.next({source: this}); // Emit before notifying the container.
dropContainer.start();
this._initialContainer = dropContainer;
Expand Down Expand Up @@ -1319,6 +1329,20 @@ export class DragRef<T = any> {
return cachedPosition ? cachedPosition.scrollPosition :
this._viewportRuler.getViewportScrollPosition();
}

/**
* Lazily resolves and returns the shadow root of the element. We do this in a function, rather
* than saving it in property directly on init, because we want to resolve it as late as possible
* in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
* constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
*/
private _getShadowRoot(): ShadowRoot | null {
if (this._cachedShadowRoot === undefined) {
this._cachedShadowRoot = _getShadowRoot(this._rootElement) as ShadowRoot | null;
}

return this._cachedShadowRoot;
}
}

/**
Expand Down Expand Up @@ -1356,11 +1380,12 @@ function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
}

/** Gets the element into which the drag preview should be inserted. */
function getPreviewInsertionPoint(documentRef: any): HTMLElement {
function getPreviewInsertionPoint(documentRef: any, shadowRoot: ShadowRoot | null): HTMLElement {
// We can't use the body if the user is in fullscreen mode,
// because the preview will render under the fullscreen element.
// TODO(crisbeto): dedupe this with the `FullscreenOverlayContainer` eventually.
return documentRef.fullscreenElement ||
return shadowRoot ||
documentRef.fullscreenElement ||
documentRef.webkitFullscreenElement ||
documentRef.mozFullScreenElement ||
documentRef.msFullscreenElement ||
Expand Down