この質問は this Githubの問題に関連しており、mat-menu
はマウスのホバーを使用して切り替えることができません。bootstrap angularマテリアルのメニューを使用したベースの水平ナビゲーションメニュー。bootstrapベースのメニューの複製を妨げているのは、ホバーでmat-menu
を開いたり閉じたりすることだけです。上記のGithubの問題で述べたように、 mouseEnter
(mouseenter)="menuTrigger.openMenu()"
またはmat-menu
をバインドするためにマットメニュー内に span を追加しますclose、
<mat-menu #menu="matMenu" overlapTrigger="false">
<span (mouseleave)="menuTrigger.closeMenu()">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</span>
</mat-menu>
しかし、どのソリューションもすべての小さなシナリオをカバーしていないようです。
例えば.
上記のGithubの問題で述べたように、最初のSOソリューションには次の問題があります。
- ボタンにマウスカーソルを合わせると、メニューがポップアップします。ただし、ボタンをクリックすると、メニューが非表示になり表示されます。私見それはバグです。
- メニューを非表示にするには、ユーザーはメニューの外側をクリックする必要があります。理想的には、マウスカーソルが外側にある場合、メニューは非表示になります
領域(ボタン、メニュー、サブメニューを含む)
400msより長い。
そして、上記の問題のいずれかを解決しようとしますが、適切に機能しないスパンソリューションでは、たとえば.
MatMenuTrigger
にカーソルを合わせるとmat-menu
が期待どおりに開きますが、ユーザーがmat-menu
を入力せずにマウスを離すと、自動的に閉じず、間違っています。
また、レベル2のサブメニューの1つに移動すると、レベル1メニューが閉じてしまいます。
PSは、開いているメニューから次の兄弟にマウスを移動しても、次のメニューは開きません。前述のように、これを達成するのは難しいと思います here 、しかし、これらのいくつかは達成できると思いますか?
これが基本的な stackBlitz です。これは私が経験していることを再現します。どんな助けにも感謝します。
最初の課題は、オーバーレイの_mat-menu
_が原因でCDKオーバーレイが生成されたときに_z-index
_がボタンからフォーカスを盗むことです...これを解決するには、スタイルにZインデックスを設定する必要がありますボタンについて...
(mouseleave)
_を追加すると、再帰ループを停止します。 _style="z-index:1050"
_次に、levelone
およびlevelTwo
メニューのすべてのEnterイベントとLeaveイベントの状態を追跡し、その状態を2つのコンポーネント変数に格納する必要があります。
_enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;
_
次に、両方のメニューレベルのmenu enterメソッドとmenuLeaveメソッドを作成します。menuLeave(trigger)
がlevel2にアクセスしているかどうかを確認し、trueの場合は何もしません。
ご注意ください:menu2Leave()
には、ナビゲーションをレベル1に戻すことができるロジックがありますが、反対側から出る場合は両方を閉じます...レベルの離脱に焦点を合わせるボタン。
_menuenter() {
this.isMatMenuOpen = true;
if (this.isMatMenu2Open) {
this.isMatMenu2Open = false;
}
}
menuLeave(trigger, button) {
setTimeout(() => {
if (!this.isMatMenu2Open && !this.enteredButton) {
this.isMatMenuOpen = false;
trigger.closeMenu();
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
} else {
this.isMatMenuOpen = false;
}
}, 80)
}
menu2enter() {
this.isMatMenu2Open = true;
}
menu2Leave(trigger1, trigger2, button) {
setTimeout(() => {
if (this.isMatMenu2Open) {
trigger1.closeMenu();
this.isMatMenuOpen = false;
this.isMatMenu2Open = false;
this.enteredButton = false;
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
} else {
this.isMatMenu2Open = false;
trigger2.closeMenu();
}
}, 100)
}
buttonEnter(trigger) {
setTimeout(() => {
if(this.prevButtonTrigger && this.prevButtonTrigger != trigger){
this.prevButtonTrigger.closeMenu();
this.prevButtonTrigger = trigger;
trigger.openMenu();
}
else if (!this.isMatMenuOpen) {
this.enteredButton = true;
this.prevButtonTrigger = trigger
trigger.openMenu()
}
else {
this.enteredButton = true;
this.prevButtonTrigger = trigger
}
})
}
buttonLeave(trigger, button) {
setTimeout(() => {
if (this.enteredButton && !this.isMatMenuOpen) {
trigger.closeMenu();
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
} if (!this.isMatMenuOpen) {
trigger.closeMenu();
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
} else {
this.enteredButton = false;
}
}, 100)
}
_
[〜#〜] html [〜#〜]
以下はそれをすべて配線する方法です。
_<ng-container *ngFor="let menuItem of modulesList">
<ng-container *ngIf="!menuItem.children">
<a class="nav-link">
<span class="icon fa" [ngClass]="menuItem.icon"></span>
<span class="text-holder">{{menuItem.label}}</span>
</a>
</ng-container>
<ng-container *ngIf="menuItem.children.length > 0">
<button #button mat-button [matMenuTriggerFor]="levelOne" #levelOneTrigger="matMenuTrigger" (mouseenter)="levelOneTrigger.openMenu()" (mouseleave)="buttonLeave(levelOneTrigger, button)" style="z-index:1050">
<span class="icon fa" [ngClass]="menuItem.icon"></span>
<span>{{menuItem.label}}
<i class="fa fa-chevron-down"></i>
</span>
</button>
<mat-menu #levelOne="matMenu" direction="down" yPosition="below">
<span (mouseenter)="menuenter()" (mouseleave)="menuLeave(levelOneTrigger, button)">
<ng-container *ngFor="let childL1 of menuItem.children">
<li class="p-0" *ngIf="!childL1.children" mat-menu-item>
<a class="nav-link">{{childL1.label}}
<i *ngIf="childL1.icon" [ngClass]="childL1.icon"></i>
</a>
</li>
<ng-container *ngIf="childL1.children && childL1.children.length > 0">
<li mat-menu-item #levelTwoTrigger="matMenuTrigger" [matMenuTriggerFor]="levelTwo">
<span class="icon fa" [ngClass]="childL1.icon"></span>
<span>{{childL1.label}}</span>
</li>
<mat-menu #levelTwo="matMenu">
<span (mouseenter)="menu2enter()" (mouseleave)="menu2Leave(levelOneTrigger,levelTwoTrigger, button)">
<ng-container *ngFor="let childL2 of childL1.children">
<li class="p-0" mat-menu-item>
<a class="nav-link">{{childL2.label}}
<i *ngIf="childL2.icon" [ngClass]="childL2.icon"></i>
</a>
</li>
</ng-container>
</span>
</mat-menu>
</ng-container>
</ng-container>
</span>
</mat-menu>
</ng-container>
</ng-container>
_
Stackblitz
https://stackblitz.com/edit/mat-nested-menu-yclrmd?embed=1&file=app/nested-menu-example.html
このソリューションは、Marshalの提案に従ってz-index:1050を設定する代わりに使用できます。他の修正については、マーシャルの答えを確認してください。
使用できます
<button [matMenuTriggerFor]="menu" #trigger="matMenuTrigger" (mouseenter)="trigger.openMenu()" (mouseleave)="trigger.closeMenu()"></button>
これを使用すると、連続的なフリッカーループが作成されますが、簡単な修正があります。
対処する必要があるのは1つだけです。
メニューが開いたとき
<div class="cdk-overlay-container"></div>
このdivは画面全体をカバーし、通常は/ bodyタグの直前のhtml全体の最後に追加されます。すべてのメニューはこのコンテナ内で生成されます。 (クラス名はバージョンによって異なる場合があります)。
これをcss/scssスタイルファイルに追加するだけです:
.cdk-overlay-container{
left:200px;
top:200px;
}
.cdk-overlay-connected-position-bounding-box{
top:0 !important;
}
または、この要素がボタンに重ならないようにするもの。
私はこれを自分で試しました。私の答えが明確で正確であるといいのですが。
これが stackblitz のデモです。問題のスタックブリッツコードを編集しました。
ここで、自動オープン/クローズマットメニューを処理するために記述したコンポーネント:
import { Component } from '@angular/core';
@Component({
selector: 'app-auto-open-menu',
template: `
<div class="app-nav-item" [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger"
(mouseenter)="mouseEnter(menuTrigger)" (mouseleave)="mouseLeave(menuTrigger)">
<ng-content select="[trigger]"></ng-content>
</div>
<mat-menu #menu="matMenu" [hasBackdrop]="false">
<div (mouseenter)="mouseEnter(menuTrigger)" (mouseleave)="mouseLeave(menuTrigger)">
<ng-content select="[content]"></ng-content>
</div>
</mat-menu>
`
})
export class AutoOpenMenuComponent {
timedOutCloser;
constructor() { }
mouseEnter(trigger) {
if (this.timedOutCloser) {
clearTimeout(this.timedOutCloser);
}
trigger.openMenu();
}
mouseLeave(trigger) {
this.timedOutCloser = setTimeout(() => {
trigger.closeMenu();
}, 50);
}
}
その後、アプリで使用できます。
<app-auto-open-menu>
<div trigger>Auto-open</div>
<div content>
<span mat-menu-item>Foo</span>
<span mat-menu-item>Bar</span>
</div>
</app-auto-open-menu>
私のために働いた最も簡単な解決策は[hasBackdrop]="false"
を追加します:
<mat-menu [hasBackdrop]="false">
</mat-menu>
私のような人なら誰でもそれを理解して避けたいですz-index: 1050
、これが完璧なソリューションです- https://stackoverflow.com/a/54630251/1122524 。
クライアントのPOCがあり、トップレベルメニューが1つしかありません。このソリューションをzインデックスとレンダラーで機能させることができました。
私のトリガーボタンはボタンでもマットボタンでもありません、それはdivです:
これらの属性は、matMenuTriggerFor属性を使用してdivに追加されました。 (menuOpened)= "isMatMenuOpen = true;" (menuClosed)= "isMatMenuOpen = false;"