Skip to content

Commit 2c0f680

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

File tree

6 files changed

+194
-101
lines changed

6 files changed

+194
-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: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import {
2222
TemplatePortal,
2323
ConnectedPositionStrategy,
2424
HorizontalConnectionPos,
25-
VerticalConnectionPos
25+
VerticalConnectionPos,
26+
OriginConnectionPosition,
27+
OverlayConnectionPosition,
28+
TransformOrigin
2629
} from '../core';
2730
import { Subscription } from 'rxjs/Subscription';
2831

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

209231
_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)