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

Sai*_*aif 10 material-design angular-material angular-material2 angular angular-material-6

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

(mouseenter)="menuTrigger.openMenu()"
Run Code Online (Sandbox Code Playgroud)

或者在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>
Run Code Online (Sandbox Code Playgroud)

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

例如

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

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

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

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

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

PS将鼠标从一个打开的菜单移动到下一个兄弟菜单不会打开下一个菜单.我想这可能是难以实现提到这里,但我认为,其中的一些可能实现的吧?

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

Mar*_*hal 17

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

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

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

enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;
Run Code Online (Sandbox Code Playgroud)

接下来为两个菜单级别创建菜单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)
  }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

Stackblitz

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

  • 我刚刚通过将buttonLeave上的setTimeout增加到100ms来更新了stackblitz链接,这应该为触发器和mat-menu过渡之间的输入菜单变量设置更多时间 (2认同)
  • 我还添加了按钮状态,该状态应解决所有其余的小问题。除此之外,请注意解决方案是完全可定制的,您将需要调整时间等以使解决方案在您的应用程序中高效。 (2认同)
  • 单击菜单上的错误(对于触摸屏必不可少)-第一次单击菜单会打开并立即折叠,在第二次单击时会保持打开状态。 (2认同)
  • @AlexKlaus这只蜜蜂已解决,请参阅修订后的stackblitz (2认同)
  • 确认,触摸屏现在很好。只看到两个小问题:1)再次单击菜单时,它被隐藏了(请参阅https://gph.is/2SBxCnL);2)第3至4次单击菜单,此菜单可能会在一秒钟内消失。 (2认同)

Pie*_*dre 17

这是我为处理自动打开/关闭 mat-menu 编写的组件:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的应用程序中使用它:

<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>
Run Code Online (Sandbox Code Playgroud)

  • 这应该是选定的答案,没有 CSS 和一个简单的完整解决方案:) (3认同)

Sor*_*ean 14

对我有用的最简单的解决方案添加[hasBackdrop]="false"

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

</mat-menu>
Run Code Online (Sandbox Code Playgroud)


小智 11

你可以通过以下方式做到这一点(这是迄今为止最好的方法):

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

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

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


Sun*_*mar 8

此解决方案可用作 Marshal 建议的设置 z-index:1050 的替代方法。对于其他修复,您应该查看 Marshal 的回答。

您可以使用

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

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

只需要处理一件事,即:

菜单打开时

<div class="cdk-overlay-container"></div>
Run Code Online (Sandbox Code Playgroud)

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

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

.cdk-overlay-container{
    left:200px;
    top:200px;
}
.cdk-overlay-connected-position-bounding-box{
    top:0 !important;

}
Run Code Online (Sandbox Code Playgroud)

或任何阻止此元素与您的按钮重叠的东西。

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

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