Skip to content

Commit 703da89

Browse files
crisbetoandrewseguin
authored andcommitted
fix(menu): reintroduce panel position classes (#11612)
Reintroduces adding CSS classes to the `mat-menu` based on its position. This is useful to some consumers which may want to style the panel based on how it's being displayed. Fixes #11597.
1 parent ba4f2af commit 703da89

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

src/lib/menu/menu-directive.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
QueryList,
2929
ViewChild,
3030
ViewEncapsulation,
31+
OnInit,
3132
} from '@angular/core';
3233
import {merge, Observable, Subject, Subscription} from 'rxjs';
3334
import {startWith, switchMap, take} from 'rxjs/operators';
@@ -97,7 +98,7 @@ const MAT_MENU_BASE_ELEVATION = 2;
9798
{provide: MAT_MENU_PANEL, useExisting: MatMenu}
9899
]
99100
})
100-
export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnDestroy {
101+
export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit, OnDestroy {
101102
private _keyManager: FocusKeyManager<MatMenuItem>;
102103
private _xPosition: MenuPositionX = this._defaultOptions.xPosition;
103104
private _yPosition: MenuPositionY = this._defaultOptions.yPosition;
@@ -141,6 +142,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
141142
throwMatMenuInvalidPositionX();
142143
}
143144
this._xPosition = value;
145+
this.setPositionClasses();
144146
}
145147

146148
/** Position of the menu in the Y axis. */
@@ -151,6 +153,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
151153
throwMatMenuInvalidPositionY();
152154
}
153155
this._yPosition = value;
156+
this.setPositionClasses();
154157
}
155158

156159
/** @docs-private */
@@ -230,6 +233,10 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
230233
private _ngZone: NgZone,
231234
@Inject(MAT_MENU_DEFAULT_OPTIONS) private _defaultOptions: MatMenuDefaultOptions) { }
232235

236+
ngOnInit() {
237+
this.setPositionClasses();
238+
}
239+
233240
ngAfterContentInit() {
234241
this._keyManager = new FocusKeyManager<MatMenuItem>(this._items).withWrap().withTypeAhead();
235242
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));
@@ -347,6 +354,21 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
347354
}
348355
}
349356

357+
/**
358+
* Adds classes to the menu panel based on its position. Can be used by
359+
* consumers to add specific styling based on the position.
360+
* @param posX Position of the menu along the x axis.
361+
* @param posY Position of the menu along the y axis.
362+
* @docs-private
363+
*/
364+
setPositionClasses(posX: MenuPositionX = this.xPosition, posY: MenuPositionY = this.yPosition) {
365+
const classes = this._classList;
366+
classes['mat-menu-before'] = posX === 'before';
367+
classes['mat-menu-after'] = posX === 'after';
368+
classes['mat-menu-above'] = posY === 'above';
369+
classes['mat-menu-below'] = posY === 'below';
370+
}
371+
350372
/** Starts the enter animation. */
351373
_startAnimation() {
352374
// @deletion-target 7.0.0 Combine with _resetAnimation.

src/lib/menu/menu.spec.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ describe('MatMenu', () => {
5050
let overlayContainerElement: HTMLElement;
5151
let focusMonitor: FocusMonitor;
5252

53-
function createComponent<T>(component: Type<T>, providers: Provider[] = [],
53+
function createComponent<T>(component: Type<T>,
54+
providers: Provider[] = [],
5455
declarations: any[] = []): ComponentFixture<T> {
5556
TestBed.configureTestingModule({
5657
imports: [MatMenuModule, NoopAnimationsModule],
@@ -491,6 +492,68 @@ describe('MatMenu', () => {
491492
}));
492493
});
493494

495+
describe('positions', () => {
496+
let fixture: ComponentFixture<PositionedMenu>;
497+
let panel: HTMLElement;
498+
499+
beforeEach(() => {
500+
fixture = createComponent(PositionedMenu);
501+
fixture.detectChanges();
502+
503+
const trigger = fixture.componentInstance.triggerEl.nativeElement;
504+
505+
// Push trigger to the bottom edge of viewport,so it has space to open "above"
506+
trigger.style.position = 'fixed';
507+
trigger.style.top = '600px';
508+
509+
// Push trigger to the right, so it has space to open "before"
510+
trigger.style.left = '100px';
511+
512+
fixture.componentInstance.trigger.openMenu();
513+
fixture.detectChanges();
514+
panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;
515+
});
516+
517+
it('should append mat-menu-before if the x position is changed', () => {
518+
expect(panel.classList).toContain('mat-menu-before');
519+
expect(panel.classList).not.toContain('mat-menu-after');
520+
521+
fixture.componentInstance.xPosition = 'after';
522+
fixture.detectChanges();
523+
524+
expect(panel.classList).toContain('mat-menu-after');
525+
expect(panel.classList).not.toContain('mat-menu-before');
526+
});
527+
528+
it('should append mat-menu-above if the y position is changed', () => {
529+
expect(panel.classList).toContain('mat-menu-above');
530+
expect(panel.classList).not.toContain('mat-menu-below');
531+
532+
fixture.componentInstance.yPosition = 'below';
533+
fixture.detectChanges();
534+
535+
expect(panel.classList).toContain('mat-menu-below');
536+
expect(panel.classList).not.toContain('mat-menu-above');
537+
});
538+
539+
it('should default to the "below" and "after" positions', () => {
540+
overlayContainer.ngOnDestroy();
541+
fixture.destroy();
542+
TestBed.resetTestingModule();
543+
544+
const newFixture = createComponent(SimpleMenu, [], [FakeIcon]);
545+
546+
newFixture.detectChanges();
547+
newFixture.componentInstance.trigger.openMenu();
548+
newFixture.detectChanges();
549+
panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;
550+
551+
expect(panel.classList).toContain('mat-menu-below');
552+
expect(panel.classList).toContain('mat-menu-after');
553+
});
554+
555+
});
556+
494557
describe('fallback positions', () => {
495558

496559
it('should fall back to "before" mode if "after" mode would not fit on screen', () => {
@@ -696,6 +759,14 @@ describe('MatMenu', () => {
696759
.toBe(Math.floor(subject.triggerRect.top),
697760
`Expected menu to open in "above" position if "below" position wouldn't fit.`);
698761
});
762+
763+
it('repositions the origin to be below, so the menu opens from the trigger', () => {
764+
subject.openMenu();
765+
subject.fixture.detectChanges();
766+
767+
expect(subject.menuPanel!.classList).toContain('mat-menu-below');
768+
expect(subject.menuPanel!.classList).not.toContain('mat-menu-above');
769+
});
699770
});
700771
});
701772

0 commit comments

Comments
 (0)