diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 06f5aa6e2014..122e30d0f2ac 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -2489,7 +2489,8 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const preview = document.querySelector('.cdk-drag-preview') as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; const previewRect = preview.getBoundingClientRect(); const zeroPxRegex = /^0(px)?$/; @@ -2511,12 +2512,14 @@ describe('CdkDrag', () => { .withContext('Expected element to be removed from layout') .toBe('-999em'); expect(item.style.opacity).withContext('Expected element to be invisible').toBe('0'); - expect(preview).withContext('Expected preview to be in the DOM').toBeTruthy(); + expect(previewContainer) + .withContext('Expected preview container to be in the DOM') + .toBeTruthy(); expect(preview.textContent!.trim()) .withContext('Expected preview content to match element') .toContain('One'); - expect(preview.getAttribute('dir')) - .withContext('Expected preview element to inherit the directionality.') + expect(previewContainer.getAttribute('dir')) + .withContext('Expected preview container element to inherit the directionality.') .toBe('ltr'); expect(previewRect.width) .withContext('Expected preview width to match element') @@ -2527,8 +2530,8 @@ describe('CdkDrag', () => { expect(preview.style.pointerEvents) .withContext('Expected pointer events to be disabled on the preview') .toBe('none'); - expect(preview.style.zIndex) - .withContext('Expected preview to have a high default zIndex.') + expect(previewContainer.style.zIndex) + .withContext('Expected preview container to have a high default zIndex.') .toBe('1000'); // Use a regex here since some browsers normalize 0 to 0px, but others don't. // Use a regex here since some browsers normalize 0 to 0px, but others don't. @@ -2549,8 +2552,8 @@ describe('CdkDrag', () => { expect(item.style.top).withContext('Expected element to be within the layout').toBeFalsy(); expect(item.style.left).withContext('Expected element to be within the layout').toBeFalsy(); expect(item.style.opacity).withContext('Expected element to be visible').toBeFalsy(); - expect(preview.parentNode) - .withContext('Expected preview to be removed from the DOM') + expect(previewContainer.parentNode) + .withContext('Expected preview container to be removed from the DOM') .toBeFalsy(); })); @@ -2568,7 +2571,7 @@ describe('CdkDrag', () => { const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const preview = document.querySelector('.cdk-drag-preview-container')! as HTMLElement; expect(preview.style.zIndex).toBe('3000'); })); @@ -2613,9 +2616,11 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); flush(); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const previewContainer = document.querySelector( + '.cdk-drag-preview-container', + )! as HTMLElement; - expect(preview.parentNode).toBe(fakeDocument.fullscreenElement); + expect(previewContainer.parentNode).toBe(fakeDocument.fullscreenElement); fakeDocument.fullscreenElement.remove(); })); @@ -2914,8 +2919,8 @@ describe('CdkDrag', () => { const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; startDraggingViaMouse(fixture, item); - expect(document.querySelector('.cdk-drag-preview')!.getAttribute('dir')) - .withContext('Expected preview element to inherit the directionality.') + expect(document.querySelector('.cdk-drag-preview-container')!.getAttribute('dir')) + .withContext('Expected preview container to inherit the directionality.') .toBe('rtl'); })); @@ -2926,7 +2931,8 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const preview = document.querySelector('.cdk-drag-preview') as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; // Add a duration since the tests won't include one. preview.style.transitionDuration = '500ms'; @@ -2939,13 +2945,13 @@ describe('CdkDrag', () => { fixture.detectChanges(); tick(250); - expect(preview.parentNode) + expect(previewContainer.parentNode) .withContext('Expected preview to be in the DOM mid-way through the transition') .toBeTruthy(); tick(500); - expect(preview.parentNode) + expect(previewContainer.parentNode) .withContext('Expected preview to be removed from the DOM if the transition timed out') .toBeFalsy(); })); @@ -3049,6 +3055,7 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; preview.style.transition = 'opacity 500ms ease'; dispatchMouseEvent(document, 'mousemove', 50, 50); @@ -3058,8 +3065,8 @@ describe('CdkDrag', () => { fixture.detectChanges(); tick(0); - expect(preview.parentNode) - .withContext('Expected preview to be removed from the DOM immediately') + expect(previewContainer.parentNode) + .withContext('Expected preview container to be removed from the DOM immediately') .toBeFalsy(); })); @@ -3071,6 +3078,7 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; preview.style.transition = 'opacity 500ms ease, transform 1000ms ease'; dispatchMouseEvent(document, 'mousemove', 50, 50); @@ -3080,15 +3088,17 @@ describe('CdkDrag', () => { fixture.detectChanges(); tick(500); - expect(preview.parentNode) - .withContext('Expected preview to be in the DOM at the end of the opacity transition') + expect(previewContainer.parentNode) + .withContext( + 'Expected preview container to be in the DOM at the end of the opacity transition', + ) .toBeTruthy(); tick(1000); - expect(preview.parentNode) + expect(previewContainer.parentNode) .withContext( - 'Expected preview to be removed from the DOM at the end of the ' + 'transform transition', + 'Expected preview container to be removed from the DOM at the end of the transform transition', ) .toBeFalsy(); })); @@ -3130,8 +3140,8 @@ describe('CdkDrag', () => { const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; - expect(preview.parentNode).toBe(document.body); + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; + expect(previewContainer.parentNode).toBe(document.body); })); it('should insert the preview into the parent node if previewContainer is set to `parent`', fakeAsync(() => { @@ -3142,9 +3152,9 @@ describe('CdkDrag', () => { const list = fixture.nativeElement.querySelector('.drop-list'); startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; expect(list).toBeTruthy(); - expect(preview.parentNode).toBe(list); + expect(previewContainer.parentNode).toBe(list); })); it('should insert the preview into a particular element, if specified', fakeAsync(() => { @@ -3158,8 +3168,10 @@ describe('CdkDrag', () => { fixture.detectChanges(); startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; - expect(preview.parentNode).toBe(previewContainer.nativeElement); + const previewContainerElement = document.querySelector( + '.cdk-drag-preview-container', + ) as HTMLElement; + expect(previewContainerElement.parentNode).toBe(previewContainer.nativeElement); })); it('should remove the id from the placeholder', fakeAsync(() => { @@ -3671,15 +3683,17 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item); - const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + const previewContainer = document.querySelector('.cdk-drag-preview-container') as HTMLElement; - expect(preview.parentNode).withContext('Expected preview to be in the DOM').toBeTruthy(); + expect(previewContainer.parentNode) + .withContext('Expected preview container to be in the DOM') + .toBeTruthy(); expect(item.parentNode).withContext('Expected drag item to be in the DOM').toBeTruthy(); fixture.destroy(); - expect(preview.parentNode) - .withContext('Expected preview to be removed from the DOM') + expect(previewContainer.parentNode) + .withContext('Expected preview container to be removed from the DOM') .toBeFalsy(); expect(item.parentNode) .withContext('Expected drag item to be removed from the DOM') @@ -6548,10 +6562,7 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, item.element.nativeElement); fixture.detectChanges(); - const initialSelectStart = dispatchFakeEvent( - shadowRoot, - 'selectstart', - ); + const initialSelectStart = dispatchFakeEvent(shadowRoot, 'selectstart'); fixture.detectChanges(); expect(initialSelectStart.defaultPrevented).toBe(true); @@ -6559,10 +6570,7 @@ describe('CdkDrag', () => { fixture.detectChanges(); flush(); - const afterDropSelectStart = dispatchFakeEvent( - shadowRoot, - 'selectstart', - ); + const afterDropSelectStart = dispatchFakeEvent(shadowRoot, 'selectstart'); fixture.detectChanges(); expect(afterDropSelectStart.defaultPrevented).toBe(false); })); diff --git a/src/cdk/drag-drop/preview-ref.ts b/src/cdk/drag-drop/preview-ref.ts index 45d81a653971..04bdc018b4b2 100644 --- a/src/cdk/drag-drop/preview-ref.ts +++ b/src/cdk/drag-drop/preview-ref.ts @@ -39,6 +39,9 @@ export class PreviewRef { /** Reference to the preview element. */ private _preview: HTMLElement; + /** Reference to the preview wrapper. */ + private _wrapper: HTMLElement; + constructor( private _document: Document, private _rootElement: HTMLElement, @@ -55,14 +58,21 @@ export class PreviewRef { ) {} attach(parent: HTMLElement): void { + this._wrapper = this._createWrapper(); this._preview = this._createPreview(); - parent.appendChild(this._preview); + this._wrapper.appendChild(this._preview); + parent.appendChild(this._wrapper); + + // The null check is necessary for browsers that don't support the popover API. + if (this._wrapper.showPopover) { + this._wrapper.showPopover(); + } } destroy(): void { - this._preview?.remove(); + this._wrapper?.remove(); this._previewEmbeddedView?.destroy(); - this._preview = this._previewEmbeddedView = null!; + this._preview = this._wrapper = this._previewEmbeddedView = null!; } setTransform(value: string): void { @@ -89,6 +99,33 @@ export class PreviewRef { this._preview.removeEventListener(name, handler); } + private _createWrapper(): HTMLElement { + const wrapper = this._document.createElement('div'); + wrapper.setAttribute('popover', 'manual'); + wrapper.setAttribute('dir', this._direction); + wrapper.classList.add('cdk-drag-preview-container'); + + extendStyles(wrapper.style, { + // This is redundant, but we need it for browsers that don't support the popover API. + 'position': 'fixed', + 'top': '0', + 'left': '0', + 'width': '100%', + 'height': '100%', + 'z-index': this._zIndex + '', + + // Reset the user agent styles. + 'background': 'none', + 'border': 'none', + 'pointer-events': 'none', + 'margin': '0', + 'padding': '0', + }); + toggleNativeDragInteractions(wrapper, false); + + return wrapper; + } + private _createPreview(): HTMLElement { const previewConfig = this._previewTemplate; const previewClass = this._previewClass; @@ -134,14 +171,12 @@ export class PreviewRef { 'position': 'absolute', 'top': '0', 'left': '0', - 'z-index': `${this._zIndex}`, }, importantProperties, ); toggleNativeDragInteractions(preview, false); preview.classList.add('cdk-drag-preview'); - preview.setAttribute('dir', this._direction); if (previewClass) { if (Array.isArray(previewClass)) {