如何在悬停时打开和关闭Angular mat菜单

问题描述 投票:6回答:2

这个问题是参考this Github问题,mat-menu无法使用鼠标悬停切换,我基本上试图用角度材料的菜单替换基于bootstrap的水平导航菜单。阻止我复制基于bootstrap的菜单的唯一一件事就是在悬停时打开和关闭mat-menu。正如上面的Github问题中所提到的,有一些解决方法可以实现我想要的,例如使用,mouseEnter

(mouseenter)="menuTrigger.openMenu()"

或者在Mat-menu中添加span以便将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>

但是没有一个解决方案似乎涵盖了每一个小场景,

EG

如上面的Github问题所述,第一个SO解决方案存在以下问题。

  • 将鼠标光标悬停在按钮上会弹出菜单。但是如果你点击按钮,它将隐藏并显示菜单。恕我直言,这是一个错误。
  • 要隐藏菜单,用户需要单击菜单外部。理想情况下,如果鼠标光标在外面,菜单将被隐藏 区域(包括按钮,菜单和子菜单) 超过400毫秒。

并且在尝试解决上述问题之一但不能正常工作的跨度解决方案中,例如,

徘徊在MatMenuTrigger确实按预期打开了mat-menu但如果用户移动鼠标而没有输入mat-menu,那么它不会自动关闭,这是错误的。

同时移动到其中一个级别的两个子菜单也关闭了一级菜单,这不是我想要的,

P.S将鼠标从一个打开的菜单移动到下一个兄弟菜单不会打开下一个菜单。我想这可能很难实现,如提到here,但我认为其中一些可能是可以实现的吗?

这是一个基本的stackBlitz,它重现了我正在经历的,任何帮助表示赞赏。

angular angular-material material-design angular-material2 angular-material-6
2个回答
10
投票

第一个挑战是当由于叠加层的mat-menu而生成CDK叠加层时,z-index从按钮窃取焦点...为了解决这个问题,你需要在按钮的样式中设置z-index ...

  • 当您向按钮添加(mouseleave)时,这将停止递归循环。 style="z-index:1050"

接下来,您需要跟踪levelonelevelTwo菜单的所有进入和离开事件的状态,并将该状态存储在两个组件变量中。

enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;

接下来为菜单级创建菜单enter和menuLeave方法。注意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>

Stackblitz

https://stackblitz.com/edit/mat-nested-menu-yclrmd?embed=1&file=app/nested-menu-example.html


0
投票

该解决方案可用作设置z-index的替代方案:Marshal建议的1050。对于其他修正,你应该检查Marshal的答案。

您可以使用

<button [matMenuTriggerFor]="menu" #trigger="matMenuTrigger" (mouseenter)="trigger.openMenu()" (mouseleave)="trigger.closeMenu()"></button>

使用它将创建连续的闪烁循环,但有一个简单的修复。

只需要处理一件事,即:

菜单打开时

<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的演示,我在问题中编辑了stackblitz代码。

© www.soinside.com 2019 - 2024. All rights reserved.