angular - 如何在悬停时打开和关闭 Angular mat 菜单
问题描述
这个问题参考了这个Github 问题,mat-menu
不能使用鼠标悬停切换,我基本上是想用角度材料的菜单替换基于引导的水平导航菜单。唯一阻止我复制基于引导程序的菜单是mat-menu
在悬停时打开和关闭。如上述 Github 问题中所述,有一些解决方法可以实现我想要的,例如使用mouseEnter
(mouseenter)="menuTrigger.openMenu()"
或在 Mat-menu 中添加一个跨度以绑定mat-menu
关闭,
<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 issue 中所述,第一个 SO 解决方案存在以下问题。
- 将鼠标光标悬停在按钮上,将弹出菜单。但是,如果您单击该按钮,它将隐藏并显示菜单。恕我直言,这是一个错误。
- 要隐藏菜单,用户需要在菜单之外单击。理想情况下,如果鼠标光标
在区域(包括按钮、菜单和子菜单)之外的
时间超过 400 毫秒,则菜单将被隐藏。
在尝试解决上述问题之一但无法正常工作的跨度解决方案中,例如
悬停MatMenuTrigger
确实会mat-menu
按预期打开,但是如果用户在没有进入的情况下将鼠标移开mat-menu
,则它不会自动关闭,这是错误的。
同样移动到二级子菜单之一也会关闭一级菜单,这不是我想要的,
PS 将鼠标从一个打开的菜单移动到下一个同级菜单不会打开下一个。我想这可能很难像这里提到的那样实现,但我认为其中一些可能是可以实现的,对吧?
这是一个基本的stackBlitz,它重现了我所经历的,任何帮助表示赞赏。
解决方案
第一个挑战是mat-menu
在生成 CDK 覆盖时由于覆盖而从按钮中窃取焦点z-index
......要解决这个问题,您需要在按钮的样式中设置 z-index...
(mouseleave)
当您向按钮添加 a 时,这将停止递归循环。style="z-index:1050"
接下来,您需要跟踪levelone
和levelTwo
菜单的所有进入和离开事件的状态,并将该状态存储在两个组件变量中。
enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;
接下来为两个菜单级别创建 menu enter 和 menuLeave 方法。notice menuLeave(trigger)
检查是否访问了 level2,如果为 true,则不执行任何操作。
请注意: menu2Leave()
有逻辑允许导航回到第一级,但如果退出另一侧则关闭两者......同时在离开关卡时移除按钮焦点。
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>
堆栈闪电战
https://stackblitz.com/edit/mat-nested-menu-yclrmd?embed=1&file=app/nested-menu-example.html
推荐阅读
- angular - 无限循环 en 获取 isAdmin 方法
- vue.js - 如何删除 chunk-vendors.js 中的注释
- javascript - 加载页面时如何防止暗模式重置?
- tensorflow - 堆叠分类器无法识别 Keras
- r - 熔化具有互斥值的列并添加原始列
- jquery - jquery 3.5.1 和 tempusdominus datetimepicker 不能一起工作
- android - 如何使用mockito在android中创建模拟api响应
- flutter - Flutter BezierChart 数据问题
- firebase - xamarin.forms listview 项目仅在滚动时更新,即使在 iNotifyPropertychanged 实施之后也是如此
- python - 命名和存储文件信息以进行比较