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

时间:2018-12-04 17:19:24

标签: angular angular-material material-design angular-material2 angular-material-6

这个问题是关于this Github问题的,mat-menu不能使用鼠标悬停来切换,我基本上是在尝试将基于引导的水平导航菜单替换为有角材质的菜单。使我无法复制基于引导程序的菜单的唯一方法是悬停时打开和关闭mat-menu。 如上述Github问题所述,有一些解决方法可以实现我想要的功能,例如使用mouseEnter

(mouseenter)="menuTrigger.openMenu()"

或在Mat-menu内添加span以便绑定mat-menu 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解决方案中存在以下问题。

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

在跨度解决方案中,该解决方案试图解决上述问题之一,但无法正常工作,例如

将鼠标悬停在MatMenuTrigger上确实会按预期方式打开mat-menu,但是如果用户在不输入mat-menu的情况下将鼠标移开,则它不会自动关闭,这是错误的。

也移至第二级子菜单之一也会关闭我不想要的第一级菜单,

P.S将鼠标从一个打开的菜单移动到下一个同级菜单不会打开下一个菜单。我猜这可能很难实现,如here所述,但是我认为其中一些可能可以实现,对吧?

这是一个基本的stackBlitz,它重现了我的经验,对您有所帮助。

11 个答案:

答案 0 :(得分:12)

第一个挑战是,由于覆盖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

答案 1 :(得分:5)

这里是我编写的用于处理自动打开/关闭垫菜单的组件:

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>

答案 2 :(得分:2)

像我这样想了解并避免使用z-index: 1050的人,这是一个完美的解决方案-https://stackoverflow.com/a/54630251/1122524

答案 3 :(得分:1)

如Marshal所建议,此解决方案可以用作设置z-index:1050的替代方法。要进行其他修复,您应该检查元帅的答案。

您可以使用

@Value("${am.issuer}")
private String clientSecret;

使用此方法将创建连续的闪烁循环,但是有一个简单的解决方法。

只需照顾一件事:

菜单打开时

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

此div覆盖整个屏幕,通常在/ body标记之前的整个html末尾添加。您的所有菜单都在此容器内生成。 (类名可能在不同版本中有所不同。)

只需将其添加到您的css / scss样式文件中即可:

<div class="cdk-overlay-container"></div>

或阻止该元素重叠按钮的任何内容。

我自己尝试过,希望我的回答清楚准确。

这里是stackblitz的演示,我已经编辑了问题中的stackblitz代码。

答案 4 :(得分:0)

最适合我的简单解决方案添加[hasBackdrop]="false"

<mat-menu [hasBackdrop]="false">

</mat-menu>

答案 5 :(得分:0)

这是另一个很好的简单解决方案,它可以在有角度的材质中创建悬停菜单,它不需要任何TS,但是您必须在根组件中添加各种相关模块的引用。

    <button mat-flat-button class="nav-item" (mouseover)="createPlanmenuTrigger.openMenu()">
        <a class="nav-link" #createPlanmenuTrigger="matMenuTrigger" [matMenuTriggerFor]="createPlan">Create Plan</a>
        <mat-menu #createPlan="matMenu">
          <button mat-menu-item>Manual Plan</button>
          <button mat-menu-item>Upload Plan</button>
          <button mat-menu-item>Pending Plans</button>
        </mat-menu>
   </button>

答案 6 :(得分:0)

使用mat菜单组件的内置方法的简单解决方案。

<ul class="navbar-nav mr-auto">
    
    <li class="nav-item" routerLinkActive="active" (mouseover)="createPlanmenuTrigger.openMenu()" (mouseout)="$event.stopPropagation();createPlanmenuTrigger.openMenu()">
        <a class="nav-link" #createPlanmenuTrigger="matMenuTrigger" [matMenuTriggerFor]="createPlan">Create Plan</a>
        <mat-menu #createPlan="matMenu">
            <button mat-menu-item [routerLink]="['/plan/manual-plan']">Manual Plan</button>
            <button mat-menu-item [routerLink]="['/plan/create-plan']">Upload Plan</button>
            <button mat-menu-item [routerLink]="['/plan/pending-plans']">Pending Plans</button>
        </mat-menu>
    </li>
</ul>

STACKBLITZ DEMO

答案 7 :(得分:0)

如果您不使用棱角材料也可以,请使用以下代码。

.dropbtn {
  background-color: #4CAF50;
  color: white;
  padding: 16px;
  font-size: 16px;
  border: none;
}

.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-content {
  display: none;
  position: absolute;
  background-color: #f1f1f1;
  min-width: 160px;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  z-index: 1;
}

.dropdown-content a {
  color: black;
  padding: 12px 16px;
  text-decoration: none;
  display: block;
}

.dropdown-content a:hover {background-color: #ddd;}

.dropdown:hover .dropdown-content {display: block;}

.dropdown:hover .dropbtn {background-color: #3e8e41;}
<div class="dropdown">
  <button class="dropbtn">Dropdown</button>
  <div class="dropdown-content">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link 3</a>
  </div>
</div>

答案 8 :(得分:0)

您可以通过以下方式进行(目前是最好的方式):

  1. 将“#locationMenuTrigger="matMenuTrigger”触发器添加到您的按钮并在此按钮上添加“(mouseenter)”事件:

    <button 
       [matMenuTriggerFor]="locationMenu"
        #locationMenuTrigger="matMenuTrigger" 
        (mouseover)="locationMenuTrigger.openMenu()"
     >info</button>
    
  2. 将您的 "(mouseleave)" 事件放在您已放入 mat-menu 的 mat-menu div/span 上,如下所示:

    <mat-menu #locationMenu="matMenu" class="location-menu">
     <span class="locations" (mouseleave)="closeMenu()">
       {{ row | notAvailable: '-' }}
     </span>
    

答案 9 :(得分:-1)

我有一个用于客户的POC,它只有一个顶层菜单。 能够使该解决方案在不使用z索引和渲染器的情况下工作。

我的触发按钮甚至不是按钮也不是matbutton,它是一个div:

使用matMenuTriggerFor属性将这些属性添加到div中。 (menuOpened)=“ isMatMenuOpen = true;” (menuClosed)=“ isMatMenuOpen = false;”

答案 10 :(得分:-1)

我在项目中使用了这种方式,但是即使在实现(mouseleave)之后应用style="z-index:1050“也不会停止递归循环。我感到非常困惑。此外,我的菜单组件是递归菜单组件,我不知道子菜单是否具有相同的触发器名称,它将正常工作。