首页 > 解决方案 > 如何在悬停时打开和关闭 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,它重现了我所经历的,任何帮助表示赞赏。

标签: angularangular-material

解决方案


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

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

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

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


推荐阅读