diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index fd8e906a8651..2de135848b1b 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -195,6 +195,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } const overlayRef = this._createOverlay(); + this._setPosition(overlayRef.getConfig().positionStrategy as FlexibleConnectedPositionStrategy); overlayRef.attach(this._portal); if (this.menu.lazyContent) { @@ -349,7 +350,9 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { */ private _getOverlayConfig(): OverlayConfig { return new OverlayConfig({ - positionStrategy: this._getPosition(), + positionStrategy: this._overlay.position() + .flexibleConnectedTo(this._element) + .withTransformOriginOn('.mat-menu-panel'), hasBackdrop: this.menu.hasBackdrop == null ? !this.triggersSubmenu() : this.menu.hasBackdrop, backdropClass: this.menu.backdropClass || 'cdk-overlay-transparent-backdrop', scrollStrategy: this._scrollStrategy(), @@ -374,11 +377,11 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } /** - * This method builds the position strategy for the overlay, so the menu is properly connected - * to the trigger. - * @returns ConnectedPositionStrategy + * Sets the appropriate positions on a position strategy + * so the overlay connects with the trigger correctly. + * @param positionStrategy Strategy whose position to update. */ - private _getPosition(): FlexibleConnectedPositionStrategy { + private _setPosition(positionStrategy: FlexibleConnectedPositionStrategy) { let [originX, originFallbackX]: HorizontalConnectionPos[] = this.menu.xPosition === 'before' ? ['end', 'start'] : ['start', 'end']; @@ -400,27 +403,24 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top'; } - return this._overlay.position() - .flexibleConnectedTo(this._element) - .withTransformOriginOn('.mat-menu-panel') - .withPositions([ - {originX, originY, overlayX, overlayY, offsetY}, - {originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY}, - { - originX, - originY: originFallbackY, - overlayX, - overlayY: overlayFallbackY, - offsetY: -offsetY - }, - { - originX: originFallbackX, - originY: originFallbackY, - overlayX: overlayFallbackX, - overlayY: overlayFallbackY, - offsetY: -offsetY - } - ]); + positionStrategy.withPositions([ + {originX, originY, overlayX, overlayY, offsetY}, + {originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY}, + { + originX, + originY: originFallbackY, + overlayX, + overlayY: overlayFallbackY, + offsetY: -offsetY + }, + { + originX: originFallbackX, + originY: originFallbackY, + overlayX: overlayFallbackX, + overlayY: overlayFallbackY, + offsetY: -offsetY + } + ]); } /** Cleans up the active subscriptions. */ diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index fe6116435198..87c73c66f5ca 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -494,13 +494,13 @@ describe('MatMenu', () => { describe('positions', () => { let fixture: ComponentFixture; - let panel: HTMLElement; + let trigger: HTMLElement; beforeEach(() => { fixture = createComponent(PositionedMenu); fixture.detectChanges(); - const trigger = fixture.componentInstance.triggerEl.nativeElement; + trigger = fixture.componentInstance.triggerEl.nativeElement; // Push trigger to the bottom edge of viewport,so it has space to open "above" trigger.style.position = 'fixed'; @@ -508,13 +508,14 @@ describe('MatMenu', () => { // Push trigger to the right, so it has space to open "before" trigger.style.left = '100px'; + }); + it('should append mat-menu-before if the x position is changed', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; - }); - it('should append mat-menu-before if the x position is changed', () => { + const panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; + expect(panel.classList).toContain('mat-menu-before'); expect(panel.classList).not.toContain('mat-menu-after'); @@ -526,6 +527,11 @@ describe('MatMenu', () => { }); it('should append mat-menu-above if the y position is changed', () => { + fixture.componentInstance.trigger.openMenu(); + fixture.detectChanges(); + + const panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; + expect(panel.classList).toContain('mat-menu-above'); expect(panel.classList).not.toContain('mat-menu-below'); @@ -546,12 +552,41 @@ describe('MatMenu', () => { newFixture.detectChanges(); newFixture.componentInstance.trigger.openMenu(); newFixture.detectChanges(); - panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; + const panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; expect(panel.classList).toContain('mat-menu-below'); expect(panel.classList).toContain('mat-menu-after'); }); + it('should be able to update the position after the first open', () => { + trigger.style.position = 'fixed'; + trigger.style.top = '200px'; + + fixture.componentInstance.yPosition = 'above'; + fixture.detectChanges(); + + fixture.componentInstance.trigger.openMenu(); + fixture.detectChanges(); + + let panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; + + expect(Math.floor(panel.getBoundingClientRect().bottom)) + .toBe(Math.floor(trigger.getBoundingClientRect().bottom), 'Expected menu to open above'); + + fixture.componentInstance.trigger.closeMenu(); + fixture.detectChanges(); + + fixture.componentInstance.yPosition = 'below'; + fixture.detectChanges(); + + fixture.componentInstance.trigger.openMenu(); + fixture.detectChanges(); + panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement; + + expect(Math.floor(panel.getBoundingClientRect().top)) + .toBe(Math.floor(trigger.getBoundingClientRect().top), 'Expected menu to open below'); + }); + }); describe('fallback positions', () => {