Skip to content

fix(menu): reintroduce panel position classes #11612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 7, 2018
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
24 changes: 23 additions & 1 deletion src/lib/menu/menu-directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
QueryList,
ViewChild,
ViewEncapsulation,
OnInit,
} from '@angular/core';
import {merge, Observable, Subject, Subscription} from 'rxjs';
import {startWith, switchMap, take} from 'rxjs/operators';
Expand Down Expand Up @@ -97,7 +98,7 @@ const MAT_MENU_BASE_ELEVATION = 2;
{provide: MAT_MENU_PANEL, useExisting: MatMenu}
]
})
export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnDestroy {
export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnInit, OnDestroy {
private _keyManager: FocusKeyManager<MatMenuItem>;
private _xPosition: MenuPositionX = this._defaultOptions.xPosition;
private _yPosition: MenuPositionY = this._defaultOptions.yPosition;
Expand Down Expand Up @@ -141,6 +142,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
throwMatMenuInvalidPositionX();
}
this._xPosition = value;
this.setPositionClasses();
}

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

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

ngOnInit() {
this.setPositionClasses();
}

ngAfterContentInit() {
this._keyManager = new FocusKeyManager<MatMenuItem>(this._items).withWrap().withTypeAhead();
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close.emit('tab'));
Expand Down Expand Up @@ -347,6 +354,21 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnD
}
}

/**
* Adds classes to the menu panel based on its position. Can be used by
* consumers to add specific styling based on the position.
* @param posX Position of the menu along the x axis.
* @param posY Position of the menu along the y axis.
* @docs-private
*/
setPositionClasses(posX: MenuPositionX = this.xPosition, posY: MenuPositionY = this.yPosition) {
const classes = this._classList;
classes['mat-menu-before'] = posX === 'before';
classes['mat-menu-after'] = posX === 'after';
classes['mat-menu-above'] = posY === 'above';
classes['mat-menu-below'] = posY === 'below';
}

/** Starts the enter animation. */
_startAnimation() {
// @deletion-target 7.0.0 Combine with _resetAnimation.
Expand Down
73 changes: 72 additions & 1 deletion src/lib/menu/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ describe('MatMenu', () => {
let overlayContainerElement: HTMLElement;
let focusMonitor: FocusMonitor;

function createComponent<T>(component: Type<T>, providers: Provider[] = [],
function createComponent<T>(component: Type<T>,
providers: Provider[] = [],
declarations: any[] = []): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [MatMenuModule, NoopAnimationsModule],
Expand Down Expand Up @@ -491,6 +492,68 @@ describe('MatMenu', () => {
}));
});

describe('positions', () => {
let fixture: ComponentFixture<PositionedMenu>;
let panel: HTMLElement;

beforeEach(() => {
fixture = createComponent(PositionedMenu);
fixture.detectChanges();

const trigger = fixture.componentInstance.triggerEl.nativeElement;

// Push trigger to the bottom edge of viewport,so it has space to open "above"
trigger.style.position = 'fixed';
trigger.style.top = '600px';

// Push trigger to the right, so it has space to open "before"
trigger.style.left = '100px';

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', () => {
expect(panel.classList).toContain('mat-menu-before');
expect(panel.classList).not.toContain('mat-menu-after');

fixture.componentInstance.xPosition = 'after';
fixture.detectChanges();

expect(panel.classList).toContain('mat-menu-after');
expect(panel.classList).not.toContain('mat-menu-before');
});

it('should append mat-menu-above if the y position is changed', () => {
expect(panel.classList).toContain('mat-menu-above');
expect(panel.classList).not.toContain('mat-menu-below');

fixture.componentInstance.yPosition = 'below';
fixture.detectChanges();

expect(panel.classList).toContain('mat-menu-below');
expect(panel.classList).not.toContain('mat-menu-above');
});

it('should default to the "below" and "after" positions', () => {
overlayContainer.ngOnDestroy();
fixture.destroy();
TestBed.resetTestingModule();

const newFixture = createComponent(SimpleMenu, [], [FakeIcon]);

newFixture.detectChanges();
newFixture.componentInstance.trigger.openMenu();
newFixture.detectChanges();
panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;

expect(panel.classList).toContain('mat-menu-below');
expect(panel.classList).toContain('mat-menu-after');
});

});

describe('fallback positions', () => {

it('should fall back to "before" mode if "after" mode would not fit on screen', () => {
Expand Down Expand Up @@ -696,6 +759,14 @@ describe('MatMenu', () => {
.toBe(Math.floor(subject.triggerRect.top),
`Expected menu to open in "above" position if "below" position wouldn't fit.`);
});

it('repositions the origin to be below, so the menu opens from the trigger', () => {
subject.openMenu();
subject.fixture.detectChanges();

expect(subject.menuPanel!.classList).toContain('mat-menu-below');
expect(subject.menuPanel!.classList).not.toContain('mat-menu-above');
});
});
});

Expand Down