Skip to content

Commit 839c2e6

Browse files
committed
refactor(cdk/overlay): account for popovers in the global position strategy
Updates the global position strategy to account for it being placed in a popover.
1 parent 59812df commit 839c2e6

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

goldens/cdk/overlay/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe
270270

271271
// @public
272272
export class GlobalPositionStrategy implements PositionStrategy {
273-
constructor(injector: Injector);
273+
constructor(injector?: Injector);
274274
apply(): void;
275275
// (undocumented)
276276
attach(overlayRef: OverlayRef): void;

src/cdk/overlay/position/global-position-strategy.spec.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import {
77
OverlayRef,
88
createOverlayRef,
99
createGlobalPositionStrategy,
10+
GlobalPositionStrategy,
11+
OverlayContainer,
1012
} from '../index';
1113

1214
describe('GlobalPositonStrategy', () => {
1315
let overlayRef: OverlayRef;
1416
let injector: Injector;
17+
let portal: ComponentPortal<BlankPortal>;
1518

1619
beforeEach(() => {
1720
injector = TestBed.inject(Injector);
@@ -25,7 +28,7 @@ describe('GlobalPositonStrategy', () => {
2528
});
2629

2730
function attachOverlay(config: OverlayConfig): OverlayRef {
28-
const portal = new ComponentPortal(BlankPortal);
31+
portal = new ComponentPortal(BlankPortal);
2932
overlayRef = createOverlayRef(injector, config);
3033
overlayRef.attach(portal);
3134
TestBed.inject(ApplicationRef).tick();
@@ -469,6 +472,50 @@ describe('GlobalPositonStrategy', () => {
469472
expect(elementStyle.marginRight).toBe('');
470473
expect(parentStyle.justifyContent).toBe('flex-end');
471474
});
475+
476+
describe('DOM location', () => {
477+
let positionStrategy: GlobalPositionStrategy;
478+
let containerElement: HTMLElement;
479+
480+
beforeEach(() => {
481+
containerElement = TestBed.inject(OverlayContainer).getContainerElement();
482+
positionStrategy = createGlobalPositionStrategy(injector);
483+
});
484+
485+
it('should place the overlay inside the overlay container by default', () => {
486+
attachOverlay({positionStrategy, usePopover: false});
487+
expect(containerElement.contains(overlayRef.hostElement)).toBe(true);
488+
expect(overlayRef.hostElement.getAttribute('popover')).toBeFalsy();
489+
});
490+
491+
it('should be able to opt into placing the overlay inside a popover element', () => {
492+
if (!('showPopover' in document.body)) {
493+
return;
494+
}
495+
496+
attachOverlay({positionStrategy, usePopover: true});
497+
498+
expect(containerElement.contains(overlayRef.hostElement)).toBe(false);
499+
expect(document.body.lastChild).toBe(overlayRef.hostElement);
500+
expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual');
501+
});
502+
503+
it('should re-attach the popover at the end of the body', () => {
504+
if (!('showPopover' in document.body)) {
505+
return;
506+
}
507+
508+
attachOverlay({positionStrategy, usePopover: true});
509+
expect(document.body.lastChild).toBe(overlayRef.hostElement);
510+
511+
overlayRef.detach();
512+
TestBed.inject(ApplicationRef).tick();
513+
expect(overlayRef.hostElement.parentNode).toBeFalsy();
514+
515+
overlayRef.attach(portal);
516+
expect(document.body.lastChild).toBe(overlayRef.hostElement);
517+
});
518+
});
472519
});
473520

474521
@Component({

src/cdk/overlay/position/global-position-strategy.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injector} from '@angular/core';
9+
import {DOCUMENT, Injector} from '@angular/core';
1010
import {OverlayRef} from '../overlay-ref';
1111
import {PositionStrategy} from './position-strategy';
1212

@@ -17,10 +17,8 @@ const wrapperClass = 'cdk-global-overlay-wrapper';
1717
* Creates a global position strategy.
1818
* @param injector Injector used to resolve dependencies for the strategy.
1919
*/
20-
export function createGlobalPositionStrategy(_injector: Injector): GlobalPositionStrategy {
21-
// Note: `injector` is unused, but we may need it in
22-
// the future which would introduce a breaking change.
23-
return new GlobalPositionStrategy();
20+
export function createGlobalPositionStrategy(injector: Injector): GlobalPositionStrategy {
21+
return new GlobalPositionStrategy(injector);
2422
}
2523

2624
/**
@@ -41,6 +39,13 @@ export class GlobalPositionStrategy implements PositionStrategy {
4139
private _width = '';
4240
private _height = '';
4341
private _isDisposed = false;
42+
private _document: Document;
43+
44+
constructor(injector?: Injector) {
45+
// TODO(crisbeto): injector should be required, but some internal apps
46+
// don't go through `createGlobalPositionStrategy` so they don't provide it.
47+
this._document = injector?.get(DOCUMENT) || document;
48+
}
4449

4550
attach(overlayRef: OverlayRef): void {
4651
const config = overlayRef.getConfig();
@@ -269,4 +274,9 @@ export class GlobalPositionStrategy implements PositionStrategy {
269274
this._overlayRef = null!;
270275
this._isDisposed = true;
271276
}
277+
278+
/** @docs-private */
279+
getPopoverInsertionPoint(): Element {
280+
return this._document.body.lastChild as Element;
281+
}
272282
}

0 commit comments

Comments
 (0)