Skip to content
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
1 change: 1 addition & 0 deletions src/cdk/drag-drop/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ ng_test_library(
deps = [
":drag-drop",
"//src/cdk/bidi",
"//src/cdk/platform",
"//src/cdk/scrolling",
"//src/cdk/testing",
"@npm//@angular/common",
Expand Down
150 changes: 98 additions & 52 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import {TestBed, ComponentFixture, fakeAsync, flush, tick} from '@angular/core/testing';
import {DOCUMENT} from '@angular/common';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {_supportsShadowDom} from '@angular/cdk/platform';
import {of as observableOf} from 'rxjs';

import {DragDropModule} from '../drag-drop-module';
Expand Down Expand Up @@ -4010,6 +4011,39 @@ describe('CdkDrag', () => {
cleanup();
}));

it('should be able to drop into a new container inside the Shadow DOM', fakeAsync(() => {
// This test is only relevant for Shadow DOM-supporting browsers.
if (!_supportsShadowDom()) {
return;
}

const fixture = createComponent(ConnectedDropZonesInsideShadowRoot);
fixture.detectChanges();

const groups = fixture.componentInstance.groupedDragItems;
const item = groups[0][1];
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();

dragElementViaMouse(fixture, item.element.nativeElement,
targetRect.left + 1, targetRect.top + 1);
flush();
fixture.detectChanges();

expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);

const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];

expect(event).toEqual({
previousIndex: 1,
currentIndex: 3,
item,
container: fixture.componentInstance.dropInstances.toArray()[1],
previousContainer: fixture.componentInstance.dropInstances.first,
isPointerOverContainer: true,
distance: {x: jasmine.any(Number), y: jasmine.any(Number)}
});
}));

});

describe('with nested drags', () => {
Expand Down Expand Up @@ -4389,65 +4423,68 @@ class DraggableInDropZoneWithCustomPlaceholder {
renderPlaceholder = true;
}

const CONNECTED_DROP_ZONES_STYLES = [`
.cdk-drop-list {
display: block;
width: 100px;
min-height: ${ITEM_HEIGHT}px;
background: hotpink;
}

@Component({
encapsulation: ViewEncapsulation.None,
styles: [`
.cdk-drop-list {
display: block;
width: 100px;
min-height: ${ITEM_HEIGHT}px;
background: hotpink;
}
.cdk-drag {
display: block;
height: ${ITEM_HEIGHT}px;
background: red;
}
`];

.cdk-drag {
display: block;
height: ${ITEM_HEIGHT}px;
background: red;
}
`],
template: `
const CONNECTED_DROP_ZONES_TEMPLATE = `
<div
cdkDropList
#todoZone="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneZone]"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
cdkDropList
#todoZone="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneZone]"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of todo"
cdkDrag>{{item}}</div>
</div>
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of todo"
cdkDrag>{{item}}</div>
</div>

<div
cdkDropList
#doneZone="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoZone]"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
cdkDropList
#doneZone="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoZone]"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of done"
cdkDrag>{{item}}</div>
</div>
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of done"
cdkDrag>{{item}}</div>
</div>

<div
cdkDropList
#extraZone="cdkDropList"
[cdkDropListData]="extra"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
cdkDropList
#extraZone="cdkDropList"
[cdkDropListData]="extra"
(cdkDropListDropped)="droppedSpy($event)"
(cdkDropListEntered)="enteredSpy($event)">
<div
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of extra"
cdkDrag>{{item}}</div>
</div>
`
[cdkDragData]="item"
(cdkDragEntered)="itemEnteredSpy($event)"
*ngFor="let item of extra"
cdkDrag>{{item}}</div>
</div>
`;

@Component({
encapsulation: ViewEncapsulation.None,
styles: CONNECTED_DROP_ZONES_STYLES,
template: CONNECTED_DROP_ZONES_TEMPLATE
})
class ConnectedDropZones implements AfterViewInit {
@ViewChildren(CdkDrag) rawDragItems: QueryList<CdkDrag>;
Expand All @@ -4472,6 +4509,15 @@ class ConnectedDropZones implements AfterViewInit {
}
}

@Component({
encapsulation: ViewEncapsulation.ShadowDom,
styles: CONNECTED_DROP_ZONES_STYLES,
template: CONNECTED_DROP_ZONES_TEMPLATE
})
class ConnectedDropZonesInsideShadowRoot extends ConnectedDropZones {
}


@Component({
encapsulation: ViewEncapsulation.None,
styles: [`
Expand Down
25 changes: 20 additions & 5 deletions src/cdk/drag-drop/drop-list-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ElementRef, NgZone} from '@angular/core';
import {Direction} from '@angular/cdk/bidi';
import {coerceElement} from '@angular/cdk/coercion';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {_supportsShadowDom} from '@angular/cdk/platform';
import {Subject, Subscription, interval, animationFrameScheduler} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {moveItemInArray} from './drag-utils';
Expand Down Expand Up @@ -74,8 +75,6 @@ export interface DropListRefInternal extends DropListRef {}
* @docs-private
*/
export class DropListRef<T = any> {
private _document: Document;

/** Element that the drop list is attached to. */
element: HTMLElement | ElementRef<HTMLElement>;

Expand Down Expand Up @@ -201,6 +200,9 @@ export class DropListRef<T = any> {
/** Used to signal to the current auto-scroll sequence when to stop. */
private _stopScrollTimers = new Subject<void>();

/** Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly. */
private _shadowRoot: DocumentOrShadowRoot;

constructor(
element: ElementRef<HTMLElement> | HTMLElement,
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
Expand All @@ -211,9 +213,9 @@ export class DropListRef<T = any> {
*/
private _ngZone?: NgZone,
private _viewportRuler?: ViewportRuler) {
const nativeNode = this.element = coerceElement(element);
this._shadowRoot = getShadowRoot(nativeNode) || _document;
_dragDropRegistry.registerDropContainer(this);
this._document = _document;
this.element = element instanceof ElementRef ? element.nativeElement : element;
}

/** Removes the drop list functionality from the DOM element. */
Expand Down Expand Up @@ -815,7 +817,7 @@ export class DropListRef<T = any> {
return false;
}

const elementFromPoint = this._document.elementFromPoint(x, y) as HTMLElement | null;
const elementFromPoint = this._shadowRoot.elementFromPoint(x, y) as HTMLElement | null;

// If there's no element at the pointer position, then
// the client rect is probably scrolled out of the view.
Expand Down Expand Up @@ -1049,3 +1051,16 @@ function getElementScrollDirections(element: HTMLElement, clientRect: ClientRect

return [verticalScrollDirection, horizontalScrollDirection];
}

/** Gets the shadow root of an element, if any. */
function getShadowRoot(element: HTMLElement): DocumentOrShadowRoot | null {
if (_supportsShadowDom()) {
const rootNode = element.getRootNode ? element.getRootNode() : null;

if (rootNode instanceof ShadowRoot) {
return rootNode;
}
}

return null;
}