Skip to content

Commit 34cd5dd

Browse files
committed
fix(menu): account for menu padding different from the default
Currently we use the hardcoded value of the menu padding to offset the panel so the two menu triggers align. Since the value is in TS, the logic breaks down if the user has set their own padding. These changes add some logic to compute the padding the first time a sub-menu is opened. Fixes #16167.
1 parent 8e321ae commit 34cd5dd

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

src/material-experimental/mdc-menu/menu.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,26 @@ describe('MatMenu', () => {
16751675
expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING);
16761676
}));
16771677

1678+
it('should account for custom padding when offsetting the sub-menu', () => {
1679+
compileTestComponent();
1680+
instance.rootTriggerEl.nativeElement.style.position = 'fixed';
1681+
instance.rootTriggerEl.nativeElement.style.left = '75px';
1682+
instance.rootTriggerEl.nativeElement.style.top = '75px';
1683+
instance.rootTrigger.openMenu();
1684+
fixture.detectChanges();
1685+
1686+
// Change the padding to something different from the default.
1687+
(overlay.querySelector('.mat-mdc-menu-content') as HTMLElement).style.padding = '15px 0';
1688+
1689+
instance.levelOneTrigger.openMenu();
1690+
fixture.detectChanges();
1691+
1692+
const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect();
1693+
const panelRect = overlay.querySelectorAll('.cdk-overlay-pane')[1].getBoundingClientRect();
1694+
1695+
expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + 15);
1696+
});
1697+
16781698
it('should close all of the menus when an item is clicked', fakeAsync(() => {
16791699
compileTestComponent();
16801700
instance.rootTriggerEl.nativeElement.click();

src/material/menu/menu-trigger.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ export const MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER = {
5858
useFactory: MAT_MENU_SCROLL_STRATEGY_FACTORY,
5959
};
6060

61-
/** Default top padding of the menu panel. */
62-
export const MENU_PANEL_TOP_PADDING = 8;
63-
6461
/** Options for binding a passive event listener. */
6562
const passiveEventListenerOptions = normalizePassiveListenerOptions({passive: true});
6663

@@ -90,6 +87,12 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
9087
private _menuCloseSubscription = Subscription.EMPTY;
9188
private _scrollStrategy: () => ScrollStrategy;
9289

90+
/**
91+
* Cached value of the padding of the parent menu panel.
92+
* Used to offset sub-menus to compensate for the padding.
93+
*/
94+
private _parentInnerPadding: number | undefined;
95+
9396
/**
9497
* Handles touch start events on the trigger.
9598
* Needs to be an arrow function so we can easily use addEventListener and removeEventListener.
@@ -449,11 +452,18 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
449452
let offsetY = 0;
450453

451454
if (this.triggersSubmenu()) {
455+
// console.log(this._parentMenu.items.first._getHostElement().offsetTop);
452456
// When the menu is a sub-menu, it should always align itself
453457
// to the edges of the trigger, instead of overlapping it.
454458
overlayFallbackX = originX = this.menu.xPosition === 'before' ? 'start' : 'end';
455459
originFallbackX = overlayX = originX === 'end' ? 'start' : 'end';
456-
offsetY = overlayY === 'bottom' ? MENU_PANEL_TOP_PADDING : -MENU_PANEL_TOP_PADDING;
460+
461+
if (this._parentInnerPadding == null) {
462+
const firstSibling = this._parentMenu.items.first;
463+
this._parentInnerPadding = firstSibling ? firstSibling._getHostElement().offsetTop : 0;
464+
}
465+
466+
offsetY = overlayY === 'bottom' ? this._parentInnerPadding : -this._parentInnerPadding;
457467
} else if (!this.menu.overlapTrigger) {
458468
originY = overlayY === 'top' ? 'bottom' : 'top';
459469
originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top';

src/material/menu/menu.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ import {
4343
MenuPositionX,
4444
MenuPositionY,
4545
} from './index';
46-
import {MAT_MENU_SCROLL_STRATEGY, MENU_PANEL_TOP_PADDING} from './menu-trigger';
46+
import {MAT_MENU_SCROLL_STRATEGY} from './menu-trigger';
4747

48+
const MENU_PANEL_TOP_PADDING = 8;
4849

4950
describe('MatMenu', () => {
5051
let overlayContainer: OverlayContainer;
@@ -1712,6 +1713,26 @@ describe('MatMenu', () => {
17121713
expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING);
17131714
}));
17141715

1716+
it('should account for custom padding when offsetting the sub-menu', () => {
1717+
compileTestComponent();
1718+
instance.rootTriggerEl.nativeElement.style.position = 'fixed';
1719+
instance.rootTriggerEl.nativeElement.style.left = '75px';
1720+
instance.rootTriggerEl.nativeElement.style.top = '75px';
1721+
instance.rootTrigger.openMenu();
1722+
fixture.detectChanges();
1723+
1724+
// Change the padding to something different from the default.
1725+
(overlay.querySelector('.mat-menu-content') as HTMLElement).style.padding = '15px 0';
1726+
1727+
instance.levelOneTrigger.openMenu();
1728+
fixture.detectChanges();
1729+
1730+
const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect();
1731+
const panelRect = overlay.querySelectorAll('.cdk-overlay-pane')[1].getBoundingClientRect();
1732+
1733+
expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + 15);
1734+
});
1735+
17151736
it('should close all of the menus when an item is clicked', fakeAsync(() => {
17161737
compileTestComponent();
17171738
instance.rootTriggerEl.nativeElement.click();

0 commit comments

Comments
 (0)