web-dev-qa-db-ja.com

開閉方法Angularホバーのマットメニュー

この質問は this Githubの問題に関連しており、mat-menuはマウスのホバーを使用して切り替えることができません。bootstrap angularマテリアルのメニューを使用したベースの水平ナビゲーションメニュー。bootstrapベースのメニューの複製を妨げているのは、ホバーでmat-menuを開いたり閉じたりすることだけです。上記のGithubの問題で述べたように、 mouseEnter

(mouseenter)="menuTrigger.openMenu()"

またはmat-menuをバインドするためにマットメニュー内に span を追加します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ソリューションには次の問題があります。

  • ボタンにマウスカーソルを合わせると、メニューがポップアップします。ただし、ボタンをクリックすると、メニューが非表示になり表示されます。私見それはバグです。
  • メニューを非表示にするには、ユーザーはメニューの外側をクリックする必要があります。理想的には、マウスカーソルが外側にある場合、メニューは非表示になります
    領域(ボタン、メニュー、サブメニューを含む)
    400msより長い。

そして、上記の問題のいずれかを解決しようとしますが、適切に機能しないスパンソリューションでは、たとえば.

MatMenuTriggerにカーソルを合わせるとmat-menuが期待どおりに開きますが、ユーザーがmat-menuを入力せずにマウスを離すと、自動的に閉じず、間違っています。

また、レベル2のサブメニューの1つに移動すると、レベル1メニューが閉じてしまいます。

PSは、開いているメニューから次の兄弟にマウスを移動しても、次のメニューは開きません。前述のように、これを達成するのは難しいと思います here 、しかし、これらのいくつかは達成できると思いますか?

これが基本的な stackBlitz です。これは私が経験していることを再現します。どんな助けにも感謝します。

16
Saif

最初の課題は、オーバーレイの_mat-menu_が原因でCDKオーバーレイが生成されたときに_z-index_がボタンからフォーカスを盗むことです...これを解決するには、スタイルにZインデックスを設定する必要がありますボタンについて...

  • これは、ボタンに_(mouseleave)_を追加すると、再帰ループを停止します。 _style="z-index:1050"_

次に、leveloneおよびlevelTwoメニューのすべてのEnterイベントとLeaveイベントの状態を追跡し、その状態を2つのコンポーネント変数に格納する必要があります。

_enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;
_

次に、両方のメニューレベルのmenu enterメソッドとmenuLeaveメソッドを作成します。menuLeave(trigger)がlevel2にアクセスしているかどうかを確認し、trueの場合は何もしません。

ご注意ください:menu2Leave()には、ナビゲーションをレベル1に戻すことができるロジックがありますが、反対側から出る場合は両方を閉じます...レベルの離脱に焦点を合わせるボタン。

_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

19
Marshal

このソリューションは、Marshalの提案に従ってz-index:1050を設定する代わりに使用できます。他の修正については、マーシャルの答えを確認してください。

使用できます

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

これを使用すると、連続的なフリッカーループが作成されますが、簡単な修正があります。

対処する必要があるのは1つだけです。

メニューが開いたとき

<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 のデモです。問題のスタックブリッツコードを編集しました。

5
Sunil Kumar

ここで、自動オープン/クローズマットメニューを処理するために記述したコンポーネント:

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

私のために働いた最も簡単な解決策は[hasBackdrop]="false"を追加します:

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

</mat-menu>
2

私のような人なら誰でもそれを理解して避けたいですz-index: 1050、これが完璧なソリューションです- https://stackoverflow.com/a/54630251/1122524

2
lyslim

クライアントのPOCがあり、トップレベルメニューが1つしかありません。このソリューションをzインデックスとレンダラーで機能させることができました。

私のトリガーボタンはボタンでもマットボタンでもありません、それはdivです:

これらの属性は、matMenuTriggerFor属性を使用してdivに追加されました。 (menuOpened)= "isMatMenuOpen = true;" (menuClosed)= "isMatMenuOpen = false;"

0
Isaac