Skip to content

Commit d893054

Browse files
committed
fix(menu): reposition menu if it would open off screen
1 parent f7012a4 commit d893054

File tree

6 files changed

+192
-101
lines changed

6 files changed

+192
-101
lines changed

src/lib/core/style/_menu-common.scss

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -47,43 +47,3 @@ $md-menu-side-padding: 16px !default;
4747
}
4848
}
4949
}
50-
51-
/**
52-
* This mixin adds the correct panel transform styles based
53-
* on the direction that the menu panel opens.
54-
*/
55-
@mixin md-menu-positions() {
56-
&.md-menu-after.md-menu-below {
57-
transform-origin: left top;
58-
}
59-
60-
&.md-menu-after.md-menu-above {
61-
transform-origin: left bottom;
62-
}
63-
64-
&.md-menu-before.md-menu-below {
65-
transform-origin: right top;
66-
}
67-
68-
&.md-menu-before.md-menu-above {
69-
transform-origin: right bottom;
70-
}
71-
72-
[dir='rtl'] & {
73-
&.md-menu-after.md-menu-below {
74-
transform-origin: right top;
75-
}
76-
77-
&.md-menu-after.md-menu-above {
78-
transform-origin: right bottom;
79-
}
80-
81-
&.md-menu-before.md-menu-below {
82-
transform-origin: left top;
83-
}
84-
85-
&.md-menu-before.md-menu-above {
86-
transform-origin: left bottom;
87-
}
88-
}
89-
}

src/lib/menu/menu-directive.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy {
4141
/** Subscription to tab events on the menu panel */
4242
private _tabSubscription: Subscription;
4343

44+
selector = '.md-menu-panel';
45+
4446
/** Config object to be passed into the menu's ngClass */
4547
_classList: any = {};
4648

@@ -54,7 +56,6 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy {
5456
@Attribute('y-position') posY: MenuPositionY) {
5557
if (posX) { this._setPositionX(posX); }
5658
if (posY) { this._setPositionY(posY); }
57-
this._setPositionClasses();
5859
}
5960

6061
// TODO: internal
@@ -83,7 +84,6 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy {
8384
obj[className] = true;
8485
return obj;
8586
}, {});
86-
this._setPositionClasses();
8787
}
8888

8989
@Output() close = new EventEmitter<void>();
@@ -119,15 +119,4 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy {
119119
this.positionY = pos;
120120
}
121121

122-
/**
123-
* It's necessary to set position-based classes to ensure the menu panel animation
124-
* folds out from the correct direction.
125-
*/
126-
private _setPositionClasses() {
127-
this._classList['md-menu-before'] = this.positionX == 'before';
128-
this._classList['md-menu-after'] = this.positionX == 'after';
129-
this._classList['md-menu-above'] = this.positionY == 'above';
130-
this._classList['md-menu-below'] = this.positionY == 'below';
131-
}
132-
133122
}

src/lib/menu/menu-panel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {MenuPositionX, MenuPositionY} from './menu-positions';
44
export interface MdMenuPanel {
55
positionX: MenuPositionX;
66
positionY: MenuPositionY;
7+
selector: string;
78
templateRef: TemplateRef<any>;
89
close: EventEmitter<void>;
910
focusFirstItem: () => void;

src/lib/menu/menu-trigger.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
TemplatePortal,
2323
ConnectedPositionStrategy,
2424
HorizontalConnectionPos,
25-
VerticalConnectionPos
25+
VerticalConnectionPos,
26+
TransformOrigin
2627
} from '../core';
2728
import { Subscription } from 'rxjs/Subscription';
2829

@@ -196,14 +197,33 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
196197
* @returns ConnectedPositionStrategy
197198
*/
198199
private _getPosition(): ConnectedPositionStrategy {
199-
const positionX: HorizontalConnectionPos = this.menu.positionX === 'before' ? 'end' : 'start';
200-
const positionY: VerticalConnectionPos = this.menu.positionY === 'above' ? 'bottom' : 'top';
201-
202-
return this._overlay.position().connectedTo(
203-
this._element,
204-
{originX: positionX, originY: positionY},
205-
{overlayX: positionX, overlayY: positionY}
206-
);
200+
const [posX, fallbackX]: HorizontalConnectionPos[] =
201+
this.menu.positionX === 'before' ? ['end', 'start'] : ['start', 'end'];
202+
203+
const [posY, fallbackY]: VerticalConnectionPos[] =
204+
this.menu.positionY === 'above' ? ['bottom', 'top'] : ['top', 'bottom'];
205+
return this._overlay.position()
206+
.connectedTo(this._element,
207+
{originX: posX, originY: posY}, {overlayX: posX, overlayY: posY})
208+
.withTransformOrigin(this.menu.selector, this._transformOrigin(posX, posY))
209+
.withFallbackPosition(
210+
{originX: fallbackX, originY: posY},
211+
{overlayX: fallbackX, overlayY: posY},
212+
this._transformOrigin(fallbackX, posY))
213+
.withFallbackPosition(
214+
{originX: posX, originY: fallbackY},
215+
{overlayX: posX, overlayY: fallbackY},
216+
this._transformOrigin(posX, fallbackY))
217+
.withFallbackPosition(
218+
{originX: fallbackX, originY: fallbackY},
219+
{overlayX: fallbackX, overlayY: fallbackY},
220+
this._transformOrigin(fallbackX, fallbackY));
221+
}
222+
223+
/** Converts the designated point into a TransformOrigin. */
224+
private _transformOrigin(x: HorizontalConnectionPos,
225+
y: VerticalConnectionPos): TransformOrigin {
226+
return `${x} ${y}` as TransformOrigin;
207227
}
208228

209229
_handleMousedown(event: MouseEvent): void {

src/lib/menu/menu.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ $md-menu-vertical-padding: 8px !default;
99

1010
.md-menu-panel {
1111
@include md-menu-base();
12-
@include md-menu-positions();
1312

1413
// max height must be 100% of the viewport height + one row height
1514
max-height: calc(100vh + 48px);

0 commit comments

Comments
 (0)